Replace the manual monitorOrder config array (a debugging artifact from a
temporarily rearranged setup) with automatic sorting by Hyprland layout
position (ascending x, ties by y). The list is rebuilt on every overlay
open, screen hotplug, and shortcut re-trigger, with refreshMonitors() and
an unpopulated-monitor (lastIpcObject) fallback to ShellScreen position.
Removes the regex JSONC parse path and the monitorOrder key from the
config schema and defaults.
The captureMonitorRequested relay now carries the initiating screen's
captureMode and action; the selector stashes both before the
targetMonitorCapture pulse and the target adopts them before capturing:
- Crop mode on screen A now opens B's crop editor instead of an
instant snip when A clicks B's monitor button
- cross-monitor right-click Edit was silently broken (target replayed
with session-default Copy; its null mouseButton skipped the override)
- sessionReset now re-pushes action alongside selectionMode, since the
adoption breaks the target's action binding
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
- circleDrawn predicate (Freehand && points.length > 1) excluded from the
click branch, so zero-displacement closed loops reach the circle branch
- points cleared at press: stale samples from a cancelled grab could
otherwise flip the new click/draw routing
- toolbar mode tabs ignore mid-drag swaps, matching the R/C key guards
- targetedRegionValid() uses a dedicated flag, not a >= 0 sign check, so
windows/layers straddling the monitor's left/top edge are selectable
- updateTargetedRegion gates each region-list hit-test by the same
enable* flag as its highlight repeater (no invisible-region selection)
- clampRegionToScreen trims overhang from the region's original far edge
instead of sliding the rect on-screen at full size over foreign content
- snip() re-validates size post-clamp (trim can zero edge-touching rects)
The swap in onCaptureModeChanged read isCropMode - a derived binding
that may not have re-evaluated inside the handler - so it skipped at
entry (stale false) and fired at exit (stale true), leaving the tab
oval on Freehand for the whole Crop session. Read captureMode directly.
Also hardened while diagnosing: ToolbarTabBar indicator binds its edge
targets directly to children[currentIndex] (drops the property Item
hop), and gains an interactive property so callers dim via opacity
instead of enabled: false.
Entering Crop while Freehand is active swaps selection to Rect; the R
key and the Rect/Freehand tabs are inert while Crop is on. Leaving Crop
keeps Rect (no restore); already-Rect swaps are no-ops.
The crop editor is a rectangle tool; a polygon mask its handles cannot
reshape only confuses. A freehand loop in Crop still seeds the editor
with its bounding box, but confirm captures the adjusted rect.
- fix: capture-mode state desync (6-press C cycle, inverted tab display).
selectionMode/captureMode now have a single owner (RegionSelection);
the toolbar receives them as read-only bindings and emits *Selected
request signals on tab clicks - no more bidirectional Synchronizer +
currentIndex write-back storms. captureMode is int (same-value writes
no longer emit change signals).
- feat: freehand (ex-Circle) true-shape capture - a polygon mask makes
pixels outside the drawn loop transparent (OCR flattens onto white;
recording stays rectangular). Mask survives the crop editor: confirm
captures loop intersected with the adjusted rect.
- feat: circle/freehand selection works in Crop mode (loop bounding box
seeds the editor); Rect/Freehand tabs inert only while editing.
- feat: R toggles Rect/Freehand; Space confirms the crop editor;
'Circle' tab relabeled Freehand (config keys keep circle names).
- docs: MANUAL, wiki, CONFIG_README, CHANGELOG updated.
CaptureMode { Instant, Crop } toggled by C or Instant/Crop toolbar tabs.
In Crop, release seeds an 8-handle + interior-move editor over the
retained frame (min-size and screen clamps); Enter or the confirm FAB
dispatches through the existing snip(), Esc cancels back to draw.
Action-agnostic: screenshots and recording both confirm through it.
New config: regionSelector.crop.minSize / handleSize.
Sub-dragThreshold (default 6px, per-axis) press-to-release movements now
classify as clicks: whole hovered window/region -> Copy/Edit per button.
Resolve the hovered target at press so zero-motion clicks have a target,
and dismiss cleanly when a sub-threshold gesture has no valid target
(previously snipped a tiny sliver past snip()'s <=0 guard). Circle mode
keeps zero tolerance via the binding-site ternary.
New config key: regionSelector.dragThreshold (0 disables; negatives
clamp to 0).
Complete the satty annotation feature on the install side:
- Install hypr-lens + hl wrapper scripts (detach on launch, sync for IPC)
and remove them on uninstall
- Add satty as an optional dependency and bundle defaults/satty.toml into
the config dir (never touches ~/.config/satty)
- Update README/MANUAL to use the hypr-lens/hl commands instead of raw
'qs --path ...'
- Add dev-update.sh helper for syncing scripts + shell.qml integration
The JSON config is watched via FileView { watchChanges: true } and reloads
live, so no restart is needed. The old tip was also harmful: 'killall
quickshell' kills every quickshell instance (including the DMS desktop
shell), and 'quickshell &' relaunches the default config, not hypr-lens.
Mirror hypr-overview's wording.
Replace the swappy edit flow with satty (preferred), driven by a bundled
config (satty -c ~/.config/hypr-lens/satty.toml) plus per-mode CLI flags,
so the edit action saves to the hypr-lens dir, copies to the clipboard,
and discards on Escape natively - with no XDG_CONFIG_HOME override, so GTK
theming stays native. Falls back to a bare swappy invocation when satty is
disabled or not installed (regionSelector.annotation.useSatty, default
true). satty owns its own notifications. Mirrors end-4's ScreenshotAction
tool toggle with the default flipped to satty.
Note: installer wiring (satty optional dep + bundled-config install) lives
in hypr-lens-install.sh, left unstaged here to avoid bundling unrelated
in-progress edits to that file.
Capture swappy's final surface via `swappy -f - -o -` (stdout on exit)
and route it through hypr-lens's own clipboard/disk handling instead of
relying on swappy's Save button, which only writes to swappy's global
save_dir (~/Desktop) and is unreachable from hypr-lens. Respects
copyAlsoSaves: clipboard always, disk file only when enabled. Removes
the fragile wl-paste hack; cleans up the empty output file on failed
saves. Diagnosed in swappy-save-button-investigation.md.
Source decoration:rounding live from HyprlandData over hyprctl IPC and
bind the window/layer selection-overlay radius to it (raw 1:1), so the
overlay corners trace real window corners. Falls back to 18 on query
failure. Removes the now-unused Appearance.rounding.windowRounding.
Add default values for sizes.elevationMargin, font.family.numbers, and
font.variableAxes.main to prevent undefined property errors when these
aren't specified in user config.
Fix Revealer widget using contentChildren instead of children, which
caused content items to not be detected properly.
- Add QtCore import for StandardPaths API
- Fix defaultMatugenPath to use writableLocation() and strip file:// prefix
- Change matugenFile.text to text() (it's a function, not property)
- Add onFileChanged: reload() for live color updates when wallpaper changes
Previously, hypr-lens was always using hardcoded fallback colors because
FileView.text was being accessed as a property instead of called as a
function, resulting in empty content.
Add symlink detection to prevent "same file" errors when QML modules
or scripts directories are symlinked to the repo (common for dev setups).
- Add is_symlink_to_source() helper function
- Skip copy operations when destination symlinks to source
- Show appropriate success message for symlinked directories
- Apply check to both install and update mode operations
- Add & disown to notify-send for proper background execution
- Clean notification format: action as title, path as body
- Swappy now respects copyAlsoSaves setting (saves clipboard if true)
- Remove auto-save from swappy; clipboard-focused by default
- Consistent messaging: "Copied to clipboard" vs "Copied & saved"
- Add background file watcher that monitors for saves while swappy is open
- Notifications fire instantly when user clicks Save (not after close)
- Supports multiple saves - each triggers a notification
- Consistent notification format: "hypr-lens" title with message in body
- Watcher polls file mtime every 0.3s with minimal overhead
- Properly cleans up watcher process when swappy closes
Visual feedback via notify-send when screenshots are:
- Copied to clipboard
- Saved to disk (shows full path)
- Failed to save (error notification)
Swappy edit mode shows notification only when file is saved,
silent when user cancels or uses swappy's clipboard copy.
- Extract resolveSavePath() and buildSaveSetup() in SnipCommands.qml
to eliminate duplicate save path logic between Copy and Edit commands
- Remove unnecessary sed JSONC comment stripping from record.sh since
config format is now plain JSON
Fixes#1 - Screenshots now properly save to disk when savePath is configured.
Changes:
- Changed config format from JSONC to JSON for reliable parsing
- Added tilde (~) expansion for all save paths
- Edit mode (swappy) now saves to configured savePath via -o flag
- Default paths: ~/Pictures/hypr-lens, ~/Videos/hypr-lens
- Added copyAlsoSaves option for users who want Copy to also save
- Copy mode (left-click) now clipboard-only by default
- Updated documentation and changelog
Closes#1
Add note that NixOS, Fedora, and other distros can manually install
by handling dependencies themselves. The QML components are distro-agnostic,
only the installer script is Arch-centric.
Add ability to right-click monitor buttons in the region selector toolbar
to capture the full monitor and open swappy for editing, mirroring the
existing right-click behavior for region selection.
Changes:
- MonitorButton: add editRequested signal and altAction handler
- OptionsToolbar: add editFullMonitor signal propagation
- RegionSelection: add editFullMonitor() function, fix action override logic
Refactors included:
- Use enum keys in commandBuilders for self-documenting code
- Extract circle bounding box calculation to RegionDragState
- Create ActionConfig.qml singleton for centralized action metadata
- Create RegionUtils.js with parsing, clamping, and sorting utilities
- Extract MonitorButton.qml component for reusability
- Consolidate 5 action methods into single openWithAction()
- Replace switch statements with table-driven command building
- Improve control animations using State/Transition pattern
Reduces ~70 lines of duplicated code while improving maintainability.
Add monitor buttons (DP-1, DP-2, etc.) to the region selector toolbar
for instant full-screen capture without drag selection.
- Add monitor buttons alongside Rect/Circle selection modes
- Support screenshot, record, and recordWithSound modes (not OCR/Search)
- Add configurable button order via `monitorOrder` in config
- Support cross-monitor capture from any toolbar
- Use regex extraction to read config from JSONC files
Security audit document explaining what the installer does:
- TL;DR table with quick security overview
- Detailed breakdown with line number references
- Package manager operations explained (sudo is optional)
- Safety features documented
- Verification commands for users to audit themselves
When running --update, now actively checks if shell.qml integration was
lost (e.g., after dotfiles update) and offers:
1. Auto-integrate with backup
2. Show manual instructions
3. Skip
Previously only showed a passive warning after completion.
Automated installer with:
- Dry-run mode for previewing changes
- QML module deployment to ~/.config/quickshell/
- Script installation to ~/.local/share/hypr-lens/
- Optional Python venv setup for OpenCV
- Auto-integration into existing quickshell configs
- Uninstall support
Includes example Hyprland keybinds for all features.
QuickShell-based UI for screen capture with:
- Region selector overlay with window/layer detection
- Material Design widgets (ripple buttons, tooltips, toolbar)
- Material shapes library for rounded polygon rendering
- Services for Hyprland data, app search, and i18n
- Global state management and configuration system