forked from Shinonome/dots-hyprland
screenshot tool: add layer regions
This commit is contained in:
@@ -23,7 +23,6 @@ QuickToggleButton {
|
|||||||
running: true
|
running: true
|
||||||
command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`]
|
command: ["bash", "-c", `test "$(hyprctl getoption animations:enabled -j | jq ".int")" -ne 0`]
|
||||||
onExited: (exitCode, exitStatus) => {
|
onExited: (exitCode, exitStatus) => {
|
||||||
console.log("Game mode toggle exited with code:", exitCode, "and status:", exitStatus)
|
|
||||||
root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit
|
root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,13 +27,14 @@ ShellRoot {
|
|||||||
property color genericContentForeground: "#ddffffff"
|
property color genericContentForeground: "#ddffffff"
|
||||||
property color selectionBorderColor: "#ddf1f1f1"
|
property color selectionBorderColor: "#ddf1f1f1"
|
||||||
property color selectionFillColor: "#33ffffff"
|
property color selectionFillColor: "#33ffffff"
|
||||||
property color windowBorderColor: "#ddd4ecff"
|
property color windowBorderColor: "#dda0c0da"
|
||||||
property color windowFillColor: "#33d4ecff"
|
property color windowFillColor: "#22a0c0da"
|
||||||
property color imageBorderColor: "#ddf1d1ff"
|
property color imageBorderColor: "#ddf1d1ff"
|
||||||
property color imageFillColor: "#33f1d1ff"
|
property color imageFillColor: "#33f1d1ff"
|
||||||
property color onBorderColor: "#ff000000"
|
property color onBorderColor: "#ff000000"
|
||||||
property real standardRounding: 4
|
property real standardRounding: 4
|
||||||
readonly property var windows: HyprlandData.windowList
|
readonly property var windows: HyprlandData.windowList
|
||||||
|
readonly property var layers: HyprlandData.layers
|
||||||
readonly property real falsePositivePreventionRatio: 0.5
|
readonly property real falsePositivePreventionRatio: 0.5
|
||||||
|
|
||||||
// Force initialization of some singletons
|
// Force initialization of some singletons
|
||||||
@@ -75,10 +76,7 @@ ShellRoot {
|
|||||||
id: regionText
|
id: regionText
|
||||||
text: regionRect.text
|
text: regionRect.text
|
||||||
color: root.genericContentForeground
|
color: root.genericContentForeground
|
||||||
anchors {
|
anchors.centerIn: parent
|
||||||
centerIn: parent
|
|
||||||
margins: regionLabelBackground.padding
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,9 +100,24 @@ ShellRoot {
|
|||||||
property bool dragging: false
|
property bool dragging: false
|
||||||
property var mouseButton: null
|
property var mouseButton: null
|
||||||
property var imageRegions: []
|
property var imageRegions: []
|
||||||
property var windowRegions: root.windows.filter(w => {
|
readonly property var windowRegions: filterWindowRegionsByLayers(
|
||||||
return w.workspace.id === panelWindow.activeWorkspaceId;
|
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 targetedRegionX: -1
|
||||||
property real targetedRegionY: -1
|
property real targetedRegionY: -1
|
||||||
@@ -131,18 +144,58 @@ ShellRoot {
|
|||||||
return unionArea > 0 ? interArea / unionArea : 0;
|
return unionArea > 0 ? interArea / unionArea : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function filterImageRegions(regions, windowRegions, threshold = 0.1) {
|
function filterOverlappingImageRegions(regions) {
|
||||||
// Remove image regions that overlap too much with any window region
|
let keep = [];
|
||||||
return regions.filter(region => {
|
let removed = new Set();
|
||||||
for (let i = 0; i < windowRegions.length; ++i) {
|
for (let i = 0; i < regions.length; ++i) {
|
||||||
if (intersectionOverUnion(region, windowRegions[i]) > threshold)
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
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) {
|
function updateTargetedRegion(x, y) {
|
||||||
|
// Image regions
|
||||||
const clickedRegion = panelWindow.imageRegions.find(region => {
|
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];
|
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;
|
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
|
// Window regions
|
||||||
const clickedWindow = panelWindow.windowRegions.find(region => {
|
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];
|
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 {
|
stdout: StdioCollector {
|
||||||
id: imageDimensionCollector
|
id: imageDimensionCollector
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
// imageRegions = JSON.parse(imageDimensionCollector.text);
|
|
||||||
imageRegions = filterImageRegions(
|
imageRegions = filterImageRegions(
|
||||||
JSON.parse(imageDimensionCollector.text),
|
JSON.parse(imageDimensionCollector.text),
|
||||||
panelWindow.windowRegions
|
panelWindow.windowRegions
|
||||||
@@ -355,6 +419,7 @@ ShellRoot {
|
|||||||
values: panelWindow.windowRegions
|
values: panelWindow.windowRegions
|
||||||
}
|
}
|
||||||
delegate: TargetRegion {
|
delegate: TargetRegion {
|
||||||
|
z: 2
|
||||||
required property var modelData
|
required property var modelData
|
||||||
targeted: !panelWindow.draggedAway &&
|
targeted: !panelWindow.draggedAway &&
|
||||||
(panelWindow.targetedRegionX === modelData.at[0]
|
(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 {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: panelWindow.imageRegions
|
values: panelWindow.imageRegions
|
||||||
}
|
}
|
||||||
delegate: TargetRegion {
|
delegate: TargetRegion {
|
||||||
|
z: 4
|
||||||
required property var modelData
|
required property var modelData
|
||||||
targeted: !panelWindow.draggedAway &&
|
targeted: !panelWindow.draggedAway &&
|
||||||
(panelWindow.targetedRegionX === modelData.at[0]
|
(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('--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('--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('--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('--k', type=int, default=3000, 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('--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('--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('--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]}')
|
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 addresses: []
|
||||||
property var windowByAddress: ({})
|
property var windowByAddress: ({})
|
||||||
property var monitors: []
|
property var monitors: []
|
||||||
|
property var layers: ({})
|
||||||
|
|
||||||
function updateWindowList() {
|
function updateWindowList() {
|
||||||
getClients.running = true
|
getClients.running = true
|
||||||
getMonitors.running = true
|
getMonitors.running = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateLayers() {
|
||||||
|
getLayers.running = true
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
updateWindowList()
|
updateWindowList()
|
||||||
|
updateLayers()
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -56,6 +62,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: getMonitors
|
id: getMonitors
|
||||||
command: ["bash", "-c", "hyprctl monitors -j | jq -c"]
|
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