mirror of
https://github.com/thesleepingsage/hypr-lens.git
synced 2026-11-01 06:56:42 +01:00
feat(region-selector): add one-click monitor capture buttons
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
This commit is contained in:
1 parent
11e73ec0d8
commit
2dc90c4e18
5 files changed
+146
-1
No files matched your search
@@ -42,6 +42,11 @@
|
||||
}
|
||||
},
|
||||
|
||||
// ─── Monitor Order ──────────────────────────────────────────
|
||||
// Monitor button order for full-screen capture (empty = auto-detect)
|
||||
// Example: ["DP-2", "DP-1"] to show DP-2 button first
|
||||
"monitorOrder": [],
|
||||
|
||||
// ─── Region Selector ────────────────────────────────────────
|
||||
// Controls the selection overlay behavior
|
||||
"regionSelector": {
|
||||
|
||||
@@ -76,6 +76,11 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Monitor Order ───────────────────────────────────────────
|
||||
// Monitor button order for full-screen capture (empty = auto-detect)
|
||||
// Example: ["DP-2", "DP-1"] to show DP-2 first
|
||||
property var monitorOrder: []
|
||||
|
||||
// ─── Region Selector ──────────────────────────────────────────
|
||||
property JsonObject regionSelector: JsonObject {
|
||||
// Auto-detection of clickable regions
|
||||
|
||||
@@ -20,8 +20,24 @@ Toolbar {
|
||||
// Use a synchronizer on these
|
||||
property var action
|
||||
property var selectionMode
|
||||
// Monitor list for full-screen capture buttons
|
||||
property var monitors: []
|
||||
// Signals
|
||||
signal dismiss()
|
||||
signal captureFullMonitor(string monitorName)
|
||||
|
||||
// Show monitor buttons only for screenshot/record modes (not OCR or Search)
|
||||
readonly property bool showMonitorButtons: {
|
||||
switch (action) {
|
||||
case RegionSelection.SnipAction.Copy:
|
||||
case RegionSelection.SnipAction.Edit:
|
||||
case RegionSelection.SnipAction.Record:
|
||||
case RegionSelection.SnipAction.RecordWithSound:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialShape {
|
||||
Layout.fillHeight: true
|
||||
@@ -79,4 +95,20 @@ Toolbar {
|
||||
}
|
||||
}
|
||||
|
||||
// Monitor capture buttons - one-click full-screen capture for each monitor
|
||||
Repeater {
|
||||
model: root.showMonitorButtons ? root.monitors : []
|
||||
delegate: ToolbarTabButton {
|
||||
required property var modelData
|
||||
required property int index
|
||||
current: false
|
||||
text: modelData.name
|
||||
materialSymbol: "desktop_windows"
|
||||
onClicked: root.captureFullMonitor(modelData.name)
|
||||
StyledToolTip {
|
||||
text: Translation.tr("Capture full screen: %1").arg(modelData.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,11 +26,43 @@ PanelWindow {
|
||||
bottom: true
|
||||
}
|
||||
|
||||
enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
|
||||
enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound }
|
||||
enum SelectionMode { RectCorners, Circle }
|
||||
property var action: RegionSelection.SnipAction.Copy
|
||||
property var selectionMode: RegionSelection.SelectionMode.RectCorners
|
||||
signal dismiss()
|
||||
|
||||
// Monitor capture support
|
||||
property var allMonitors: []
|
||||
property string targetMonitorCapture: ""
|
||||
signal captureMonitorRequested(string monitorName)
|
||||
|
||||
// Watch for cross-monitor capture requests (only when UI is visible and ready)
|
||||
onTargetMonitorCaptureChanged: {
|
||||
if (!root.visible || targetMonitorCapture === "" || targetMonitorCapture !== root.hyprlandMonitor.name) return;
|
||||
captureFullMonitorLocal();
|
||||
}
|
||||
|
||||
// Capture the full region of this monitor
|
||||
function captureFullMonitorLocal() {
|
||||
dragState.regionX = 0;
|
||||
dragState.regionY = 0;
|
||||
dragState.regionWidth = root.screen.width;
|
||||
dragState.regionHeight = root.screen.height;
|
||||
dragState.endDrag();
|
||||
root.snip();
|
||||
}
|
||||
|
||||
// Handle monitor button click - capture full screen of the specified monitor
|
||||
function captureFullMonitor(monitorName: string) {
|
||||
if (monitorName === root.hyprlandMonitor.name) {
|
||||
// This is our monitor, capture locally
|
||||
captureFullMonitorLocal();
|
||||
} else {
|
||||
// Request parent to coordinate capture on the target monitor
|
||||
root.captureMonitorRequested(monitorName);
|
||||
}
|
||||
}
|
||||
|
||||
property string saveScreenshotDir: Config.options.screenSnip.savePath !== ""
|
||||
? Config.options.screenSnip.savePath
|
||||
@@ -370,6 +402,7 @@ PanelWindow {
|
||||
spacing: 6
|
||||
|
||||
OptionsToolbar {
|
||||
monitors: root.allMonitors
|
||||
Synchronizer on action {
|
||||
property alias source: root.action
|
||||
}
|
||||
@@ -377,6 +410,7 @@ PanelWindow {
|
||||
property alias source: root.selectionMode
|
||||
}
|
||||
onDismiss: root.dismiss();
|
||||
onCaptureFullMonitor: (monitorName) => root.captureFullMonitor(monitorName)
|
||||
}
|
||||
Item {
|
||||
anchors {
|
||||
|
||||
@@ -19,10 +19,76 @@ Scope {
|
||||
|
||||
function dismiss() {
|
||||
GlobalStates.regionSelectorOpen = false
|
||||
root.targetMonitorCapture = "" // Reset cross-monitor capture state
|
||||
}
|
||||
|
||||
property var action: RegionSelection.SnipAction.Copy
|
||||
property var selectionMode: RegionSelection.SelectionMode.RectCorners
|
||||
|
||||
// Monitor list - updated imperatively when config loads
|
||||
property var monitorList: []
|
||||
|
||||
// Read monitor order from config and rebuild monitor list
|
||||
FileView {
|
||||
id: monitorOrderFileView
|
||||
path: Directories.shellConfigPath
|
||||
onLoaded: root.rebuildMonitorList()
|
||||
}
|
||||
|
||||
Component.onCompleted: rebuildMonitorList()
|
||||
|
||||
function rebuildMonitorList() {
|
||||
let customOrder = [];
|
||||
try {
|
||||
const rawText = monitorOrderFileView.text();
|
||||
// Extract monitorOrder array directly with regex
|
||||
const match = rawText.match(/"monitorOrder"\s*:\s*\[([^\]]*)\]/);
|
||||
if (match && match[1]) {
|
||||
// Parse the array contents: "DP-1", "DP-3", "DP-2"
|
||||
const items = match[1].match(/"([^"]+)"/g);
|
||||
if (items) {
|
||||
customOrder = items.map(s => s.replace(/"/g, ''));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
customOrder = [];
|
||||
}
|
||||
|
||||
let list = [];
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
const screen = Quickshell.screens[i];
|
||||
const monitor = Hyprland.monitorFor(screen);
|
||||
list.push({
|
||||
name: monitor.name,
|
||||
x: monitor.x,
|
||||
y: monitor.y,
|
||||
width: screen.width,
|
||||
height: screen.height,
|
||||
scale: monitor.scale
|
||||
});
|
||||
}
|
||||
|
||||
// Apply custom ordering from config if specified
|
||||
if (customOrder && customOrder.length > 0) {
|
||||
list.sort((a, b) => {
|
||||
const aIndex = customOrder.indexOf(a.name);
|
||||
const bIndex = customOrder.indexOf(b.name);
|
||||
if (aIndex === -1 && bIndex === -1) return 0;
|
||||
if (aIndex === -1) return 1;
|
||||
if (bIndex === -1) return -1;
|
||||
return aIndex - bIndex;
|
||||
});
|
||||
}
|
||||
|
||||
root.monitorList = list;
|
||||
}
|
||||
|
||||
// Cross-monitor capture coordination: set this to trigger capture on that monitor
|
||||
property string targetMonitorCapture: ""
|
||||
|
||||
function captureMonitor(monitorName: string) {
|
||||
root.targetMonitorCapture = monitorName;
|
||||
}
|
||||
|
||||
Variants {
|
||||
model: Quickshell.screens
|
||||
@@ -33,7 +99,10 @@ Scope {
|
||||
|
||||
sourceComponent: RegionSelection {
|
||||
screen: regionSelectorLoader.modelData
|
||||
allMonitors: root.monitorList
|
||||
targetMonitorCapture: root.targetMonitorCapture
|
||||
onDismiss: root.dismiss()
|
||||
onCaptureMonitorRequested: (monitorName) => root.captureMonitor(monitorName)
|
||||
action: root.action
|
||||
selectionMode: root.selectionMode
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user