screenshot tool: add layer regions

This commit is contained in:
end-4
2025-06-29 00:23:19 +02:00
parent 4b17b1aae7
commit 36db4ae327
4 changed files with 131 additions and 18 deletions
@@ -23,7 +23,6 @@ QuickToggleButton {
running: true
command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`]
onExited: (exitCode, exitStatus) => {
console.log("Game mode toggle exited with code:", exitCode, "and status:", exitStatus)
root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit
}
}
+112 -15
View File
@@ -27,13 +27,14 @@ ShellRoot {
property color genericContentForeground: "#ddffffff"
property color selectionBorderColor: "#ddf1f1f1"
property color selectionFillColor: "#33ffffff"
property color windowBorderColor: "#ddd4ecff"
property color windowFillColor: "#33d4ecff"
property color windowBorderColor: "#dda0c0da"
property color windowFillColor: "#22a0c0da"
property color imageBorderColor: "#ddf1d1ff"
property color imageFillColor: "#33f1d1ff"
property color onBorderColor: "#ff000000"
property real standardRounding: 4
readonly property var windows: HyprlandData.windowList
readonly property var layers: HyprlandData.layers
readonly property real falsePositivePreventionRatio: 0.5
// Force initialization of some singletons
@@ -75,10 +76,7 @@ ShellRoot {
id: regionText
text: regionRect.text
color: root.genericContentForeground
anchors {
centerIn: parent
margins: regionLabelBackground.padding
}
anchors.centerIn: parent
}
}
}
@@ -102,9 +100,24 @@ ShellRoot {
property bool dragging: false
property var mouseButton: null
property var imageRegions: []
property var windowRegions: root.windows.filter(w => {
return w.workspace.id === panelWindow.activeWorkspaceId;
})
readonly property var windowRegions: filterWindowRegionsByLayers(
root.windows.filter(w => w.workspace.id === panelWindow.activeWorkspaceId),
panelWindow.layerRegions
)
readonly property var layerRegions: {
const layersOfThisMonitor = root.layers[panelWindow.hyprlandMonitor.name]
const topLayers = layersOfThisMonitor.levels["2"]
const nonBarTopLayers = topLayers
.filter(layer => !(layer.namespace.includes(":bar")))
.map(layer => {
return {
at: [layer.x, layer.y],
size: [layer.w, layer.h],
namespace: layer.namespace,
}
})
return nonBarTopLayers;
}
property real targetedRegionX: -1
property real targetedRegionY: -1
@@ -131,18 +144,58 @@ ShellRoot {
return unionArea > 0 ? interArea / unionArea : 0;
}
function filterImageRegions(regions, windowRegions, threshold = 0.1) {
// Remove image regions that overlap too much with any window region
return regions.filter(region => {
for (let i = 0; i < windowRegions.length; ++i) {
if (intersectionOverUnion(region, windowRegions[i]) > threshold)
function filterOverlappingImageRegions(regions) {
let keep = [];
let removed = new Set();
for (let i = 0; i < regions.length; ++i) {
if (removed.has(i)) continue;
let regionA = regions[i];
for (let j = i + 1; j < regions.length; ++j) {
if (removed.has(j)) continue;
let regionB = regions[j];
if (intersectionOverUnion(regionA, regionB) > 0) {
// Compare areas
let areaA = regionA.size[0] * regionA.size[1];
let areaB = regionB.size[0] * regionB.size[1];
if (areaA <= areaB) {
removed.add(j);
} else {
removed.add(i);
}
}
}
}
for (let i = 0; i < regions.length; ++i) {
if (!removed.has(i)) keep.push(regions[i]);
}
return keep;
}
function filterWindowRegionsByLayers(windowRegions, layerRegions) {
return windowRegions.filter(windowRegion => {
for (let i = 0; i < layerRegions.length; ++i) {
if (intersectionOverUnion(windowRegion, layerRegions[i]) > 0)
return false;
}
return true;
});
}
function filterImageRegions(regions, windowRegions, threshold = 0.1) {
// Remove image regions that overlap too much with any window region
let filtered = regions.filter(region => {
for (let i = 0; i < windowRegions.length; ++i) {
if (intersectionOverUnion(region, windowRegions[i]) > threshold)
return false;
}
return true;
});
// Remove overlapping image regions, keep only the smaller one
return filterOverlappingImageRegions(filtered);
}
function updateTargetedRegion(x, y) {
// Image regions
const clickedRegion = panelWindow.imageRegions.find(region => {
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
});
@@ -154,6 +207,18 @@ ShellRoot {
return;
}
// Layer regions
const clickedLayer = panelWindow.layerRegions.find(region => {
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
});
if (clickedLayer) {
panelWindow.targetedRegionX = clickedLayer.at[0];
panelWindow.targetedRegionY = clickedLayer.at[1];
panelWindow.targetedRegionWidth = clickedLayer.size[0];
panelWindow.targetedRegionHeight = clickedLayer.size[1];
return;
}
// Window regions
const clickedWindow = panelWindow.windowRegions.find(region => {
return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];
@@ -210,7 +275,6 @@ ShellRoot {
stdout: StdioCollector {
id: imageDimensionCollector
onStreamFinished: {
// imageRegions = JSON.parse(imageDimensionCollector.text);
imageRegions = filterImageRegions(
JSON.parse(imageDimensionCollector.text),
panelWindow.windowRegions
@@ -355,6 +419,7 @@ ShellRoot {
values: panelWindow.windowRegions
}
delegate: TargetRegion {
z: 2
required property var modelData
targeted: !panelWindow.draggedAway &&
(panelWindow.targetedRegionX === modelData.at[0]
@@ -380,11 +445,43 @@ ShellRoot {
}
}
Repeater {
model: ScriptModel {
values: panelWindow.layerRegions
}
delegate: TargetRegion {
z: 3
required property var modelData
targeted: !panelWindow.draggedAway &&
(panelWindow.targetedRegionX === modelData.at[0]
&& panelWindow.targetedRegionY === modelData.at[1]
&& panelWindow.targetedRegionWidth === modelData.size[0]
&& panelWindow.targetedRegionHeight === modelData.size[1])
opacity: panelWindow.draggedAway ? 0 : 1
visible: opacity > 0
Behavior on opacity {
animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)
}
x: modelData.at[0]
y: modelData.at[1]
width: modelData.size[0]
height: modelData.size[1]
borderColor: root.windowBorderColor
fillColor: targeted ? root.windowFillColor : "transparent"
border.width: targeted ? 4 : 2
text: `${modelData.namespace}`
radius: Appearance.rounding.windowRounding
}
}
Repeater {
model: ScriptModel {
values: panelWindow.imageRegions
}
delegate: TargetRegion {
z: 4
required property var modelData
targeted: !panelWindow.draggedAway &&
(panelWindow.targetedRegionX === modelData.at[0]
@@ -87,8 +87,8 @@ def main():
parser.add_argument('--max-height', type=int, help='Maximum height of detected region')
parser.add_argument('--single', action='store_true', help='Only output the most likely (largest) region')
parser.add_argument('--quality', action='store_true', help='Use quality mode for selective search (slower, less sensitive)')
parser.add_argument('--k', type=int, default=35000, help='Segmentation parameter k (default: 150)')
parser.add_argument('--min-size', type=int, default=150, help='Segmentation parameter min_size (default: 20)')
parser.add_argument('--k', type=int, default=3000, help='Segmentation parameter k (default: 150)')
parser.add_argument('--min-size', type=int, default=50, help='Segmentation parameter min_size (default: 20)')
parser.add_argument('--sigma', type=float, default=0.6, help='Segmentation parameter sigma (default: 0.8)')
parser.add_argument('--resize-factor', type=float, default=0.1, help='Resize factor for input image before processing (default: 1.0, e.g. 0.5 for half size)')
parser.add_argument('--hyprctl', action='store_true', help='Mimics hyprctl\'s window output, like {"at": [x, y], "size": [w, h]}')
@@ -16,14 +16,20 @@ Singleton {
property var addresses: []
property var windowByAddress: ({})
property var monitors: []
property var layers: ({})
function updateWindowList() {
getClients.running = true
getMonitors.running = true
}
function updateLayers() {
getLayers.running = true
}
Component.onCompleted: {
updateWindowList()
updateLayers()
}
Connections {
@@ -56,6 +62,7 @@ Singleton {
}
}
}
Process {
id: getMonitors
command: ["bash", "-c", "hyprctl monitors -j | jq -c"]
@@ -65,5 +72,15 @@ Singleton {
}
}
}
Process {
id: getLayers
command: ["bash", "-c", "hyprctl layers -j | jq -c"]
stdout: SplitParser {
onRead: (data) => {
root.layers = JSON.parse(data)
}
}
}
}