mirror of
https://github.com/thesleepingsage/hypr-lens.git
synced 2026-11-01 06:56:42 +01:00
fix(regionSelector): monitor buttons follow left-to-right layout order
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.
This commit is contained in:
1 parent
5344908036
commit
29537f64ab
5 files changed
+58
-65
No files matched your search
@@ -18,6 +18,11 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
|
||||
|
||||
- **Region Selector Click/Drag Dead-Zone** - Press-to-release movements within `dragThreshold` (default `6` px) are now treated as clicks that select the whole hovered window/region, instead of snipping a tiny drag rectangle. This is a user-visible behavior change: if you relied on tiny deliberate drags to grab very small rectangles, lower or zero the threshold
|
||||
- **Region Selector Empty-Space Clicks** - A click (or sub-threshold micro-drag) over empty space with no targetable region now dismisses the overlay cleanly instead of capturing a sliver
|
||||
- **Monitor Button Order** - The toolbar's monitor capture buttons now follow left-to-right layout order automatically (ascending x, ties broken top-to-bottom, from Hyprland logical coordinates), re-read every time the selector opens — no configuration needed
|
||||
|
||||
### Removed
|
||||
|
||||
- **`monitorOrder` Config Key** - The manual monitor button ordering array is gone; ordering is now automatic from the layout position. A leftover key in an existing config is ignored and dropped the next time the config is rewritten
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
"useCircleSelection": false
|
||||
}
|
||||
},
|
||||
"monitorOrder": [],
|
||||
"regionSelector": {
|
||||
"dragThreshold": 6,
|
||||
"targetRegions": {
|
||||
|
||||
@@ -77,11 +77,6 @@ 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 {
|
||||
property int dragThreshold: 6 // Click/drag dead-zone in px (0 = disabled)
|
||||
|
||||
@@ -36,31 +36,52 @@ Scope {
|
||||
// ─── Monitor List Management ─────────────────────────────────────────────────
|
||||
property var monitorList: []
|
||||
|
||||
FileView {
|
||||
id: monitorOrderFileView
|
||||
path: Directories.shellConfigPath
|
||||
onLoaded: root.rebuildMonitorList()
|
||||
}
|
||||
|
||||
Component.onCompleted: rebuildMonitorList()
|
||||
|
||||
// Builds the monitor list from Quickshell.screens and applies custom ordering.
|
||||
// Uses regex to parse monitorOrder from JSONC config (handles comments/trailing commas).
|
||||
// Re-snapshot the monitor list every time the overlay opens: Hyprland's monitor
|
||||
// data may not be populated yet at Component.onCompleted (startup race), and
|
||||
// monitors can be hotplugged or rearranged between opens. The overlay Loaders
|
||||
// below react to the same signal with no guaranteed handler order — safe only
|
||||
// because allMonitors is a live binding, not a one-shot read.
|
||||
Connections {
|
||||
target: GlobalStates
|
||||
function onRegionSelectorOpenChanged() {
|
||||
if (GlobalStates.regionSelectorOpen) root.rebuildMonitorList();
|
||||
}
|
||||
}
|
||||
|
||||
// Hotplug while the overlay is open: the Variants below tracks Quickshell.screens
|
||||
// live, so the button row must follow too or it desyncs from the overlay windows
|
||||
Connections {
|
||||
target: Quickshell
|
||||
function onScreensChanged() {
|
||||
if (GlobalStates.regionSelectorOpen) root.rebuildMonitorList();
|
||||
}
|
||||
}
|
||||
|
||||
// Builds the monitor list from Quickshell.screens, sorted by layout position
|
||||
// (Hyprland logical coordinates): left-to-right, ties top-to-bottom.
|
||||
function rebuildMonitorList() {
|
||||
// Parse custom order from config
|
||||
const rawText = monitorOrderFileView.text();
|
||||
const customOrder = RegionUtils.parseMonitorOrder(rawText);
|
||||
// Ask Hyprland to re-fetch monitor state; some rearrangements emit no event.
|
||||
// Async — this rebuild reads current values, the refresh benefits the next one
|
||||
Hyprland.refreshMonitors();
|
||||
|
||||
// Build monitor info list
|
||||
let list = [];
|
||||
for (let i = 0; i < Quickshell.screens.length; i++) {
|
||||
const screen = Quickshell.screens[i];
|
||||
const monitor = Hyprland.monitorFor(screen);
|
||||
list.push(RegionUtils.buildMonitorInfo(screen, monitor));
|
||||
// monitorFor creates its object eagerly; x/y stay 0 until IPC data
|
||||
// arrives, so treat unpopulated the same as missing. Only warn when the
|
||||
// overlay is open — the startup rebuild is replaced at first open anyway
|
||||
const populated = monitor && monitor.lastIpcObject;
|
||||
if (!populated && GlobalStates.regionSelectorOpen)
|
||||
console.warn("RegionSelector: Hyprland monitor data unavailable for " + screen.name + "; using ShellScreen position");
|
||||
list.push(RegionUtils.buildMonitorInfo(screen, populated ? monitor : null));
|
||||
}
|
||||
|
||||
// Apply custom ordering
|
||||
root.monitorList = RegionUtils.sortMonitorsByOrder(list, customOrder);
|
||||
// Sort by layout position
|
||||
root.monitorList = RegionUtils.sortMonitorsByPosition(list);
|
||||
}
|
||||
|
||||
// ─── Cross-Monitor Capture Coordination ──────────────────────────────────────
|
||||
@@ -130,6 +151,9 @@ Scope {
|
||||
// Opens the region selector with the specified action.
|
||||
// Selection mode is determined by config or defaults to RectCorners.
|
||||
function openWithAction(action) {
|
||||
// Re-triggering while already open emits no regionSelectorOpenChanged, so
|
||||
// rebuild explicitly to pick up mid-session monitor rearrangement
|
||||
root.rebuildMonitorList();
|
||||
root.action = action;
|
||||
root.selectionMode = getSelectionModeForAction(action);
|
||||
root.captureMode = RegionSelection.CaptureMode.Instant; // Never persists across opens
|
||||
|
||||
@@ -2,26 +2,6 @@
|
||||
// This is a Qt/QML JavaScript library (.pragma library makes it stateless)
|
||||
.pragma library
|
||||
|
||||
// Parse monitorOrder from raw JSONC config text using regex.
|
||||
// This approach bypasses JSON parsing issues with JSONC (comments + trailing commas).
|
||||
// Returns an array of monitor names, or empty array if parsing fails.
|
||||
function parseMonitorOrder(rawConfigText) {
|
||||
try {
|
||||
// Match "monitorOrder": ["name1", "name2", ...]
|
||||
const match = rawConfigText.match(/"monitorOrder"\s*:\s*\[([^\]]*)\]/);
|
||||
if (match && match[1]) {
|
||||
// Extract quoted strings from array contents
|
||||
const items = match[1].match(/"([^"]+)"/g);
|
||||
if (items) {
|
||||
return items.map(s => s.replace(/"/g, ''));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Silent failure - return empty array
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
// Clamp a region to fit within screen bounds.
|
||||
// Returns a new object with clamped coordinates; does not modify input.
|
||||
function clampRegionToScreen(region, screenWidth, screenHeight) {
|
||||
@@ -41,39 +21,29 @@ function clampRegionToScreen(region, screenWidth, screenHeight) {
|
||||
};
|
||||
}
|
||||
|
||||
// Sort monitors by custom order from config.
|
||||
// Monitors in customOrder appear first in that order.
|
||||
// Monitors not in customOrder appear after, in their original order.
|
||||
// Sort monitors by layout position (Hyprland logical coordinates):
|
||||
// ascending x, ties broken by ascending y (left-to-right, then top-to-bottom).
|
||||
// Returns a new sorted array; does not modify input.
|
||||
function sortMonitorsByOrder(monitors, customOrder) {
|
||||
if (!customOrder || customOrder.length === 0) {
|
||||
return monitors;
|
||||
}
|
||||
|
||||
return [...monitors].sort((a, b) => {
|
||||
const aIdx = customOrder.indexOf(a.name);
|
||||
const bIdx = customOrder.indexOf(b.name);
|
||||
|
||||
// Both not in custom order - preserve original order
|
||||
if (aIdx === -1 && bIdx === -1) return 0;
|
||||
// Only a not in custom order - a goes after b
|
||||
if (aIdx === -1) return 1;
|
||||
// Only b not in custom order - b goes after a
|
||||
if (bIdx === -1) return -1;
|
||||
// Both in custom order - sort by index
|
||||
return aIdx - bIdx;
|
||||
});
|
||||
function sortMonitorsByPosition(monitors) {
|
||||
return [...monitors].sort((a, b) => a.x - b.x || a.y - b.y);
|
||||
}
|
||||
|
||||
// Build a monitor info object from Quickshell screen and Hyprland monitor data.
|
||||
// Used to create the monitor list for the capture buttons.
|
||||
// x/y are the layout position (Hyprland logical coordinates). A null
|
||||
// hyprlandMonitor falls back to ShellScreen's virtual desktop position, which
|
||||
// tracks the same layout in practice; the caller logs the fallback.
|
||||
function buildMonitorInfo(screen, hyprlandMonitor) {
|
||||
if (!hyprlandMonitor) {
|
||||
return {
|
||||
name: screen.name,
|
||||
x: screen.x,
|
||||
y: screen.y
|
||||
};
|
||||
}
|
||||
return {
|
||||
name: hyprlandMonitor.name,
|
||||
x: hyprlandMonitor.x,
|
||||
y: hyprlandMonitor.y,
|
||||
width: screen.width,
|
||||
height: screen.height,
|
||||
scale: hyprlandMonitor.scale
|
||||
y: hyprlandMonitor.y
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user