forked from Shinonome/dots-hyprland
screenshot tool: add layer regions
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user