mirror of
https://github.com/kossLAN/dots.git
synced 2025-11-04 22:49:50 -05:00
feat: screenshot tool
This commit is contained in:
parent
de23a67917
commit
6f39dae2ea
8 changed files with 343 additions and 17 deletions
|
|
@ -11,7 +11,7 @@ Singleton {
|
||||||
FileView {
|
FileView {
|
||||||
path: `${Quickshell.env("XDG_DATA_HOME")}/quickshell/settings.json`
|
path: `${Quickshell.env("XDG_DATA_HOME")}/quickshell/settings.json`
|
||||||
watchChanges: true
|
watchChanges: true
|
||||||
// onFileChanged: reload()
|
onFileChanged: reload()
|
||||||
onAdapterUpdated: writeAdapter()
|
onAdapterUpdated: writeAdapter()
|
||||||
blockLoading: true
|
blockLoading: true
|
||||||
|
|
||||||
|
|
@ -22,6 +22,7 @@ Singleton {
|
||||||
property int barHeight: 25
|
property int barHeight: 25
|
||||||
property string wallpaperUrl: Qt.resolvedUrl("root:resources/wallpapers/pixelart0.jpg")
|
property string wallpaperUrl: Qt.resolvedUrl("root:resources/wallpapers/pixelart0.jpg")
|
||||||
property string colorScheme: "scheme-fruit-salad"
|
property string colorScheme: "scheme-fruit-salad"
|
||||||
|
property string screenshotPath: "/home/koss/Pictures"
|
||||||
}
|
}
|
||||||
|
|
||||||
property var colors: {
|
property var colors: {
|
||||||
|
|
|
||||||
24
bar/Bar.qml
24
bar/Bar.qml
|
|
@ -78,18 +78,18 @@ PanelWindow {
|
||||||
// bar: root
|
// bar: root
|
||||||
// }
|
// }
|
||||||
|
|
||||||
Text {
|
// Text {
|
||||||
text: "home"
|
// text: "home"
|
||||||
color: "white"
|
// color: "white"
|
||||||
font.family: "Material Symbols Rounded"
|
// font.family: "Material Symbols Rounded"
|
||||||
renderType: Text.NativeRendering
|
// renderType: Text.NativeRendering
|
||||||
textFormat: Text.PlainText
|
// textFormat: Text.PlainText
|
||||||
font.pointSize: 14
|
// font.pointSize: 14
|
||||||
|
//
|
||||||
font.variableAxes: {
|
// font.variableAxes: {
|
||||||
"FILL": 0
|
// "FILL": 0
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
BatteryIndicator {
|
BatteryIndicator {
|
||||||
id: batteryIndicator
|
id: batteryIndicator
|
||||||
|
|
|
||||||
101
screencapture/Controller.qml
Normal file
101
screencapture/Controller.qml
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
pragma Singleton
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import QtQuick
|
||||||
|
import ".."
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool windowOpen: false
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "screencapture"
|
||||||
|
|
||||||
|
function screenshot(): void {
|
||||||
|
root.windowOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just use this window to grab screen context
|
||||||
|
LazyLoader {
|
||||||
|
activeAsync: root.windowOpen
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: focusedScreen
|
||||||
|
color: "transparent"
|
||||||
|
exclusionMode: ExclusionMode.Ignore
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
WlrLayershell.namespace: "shell:screencapture"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: true
|
||||||
|
bottom: true
|
||||||
|
left: true
|
||||||
|
right: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: true
|
||||||
|
Keys.onEscapePressed: root.windowOpen = false
|
||||||
|
|
||||||
|
// to get a freeze frame for now
|
||||||
|
ScreencopyView {
|
||||||
|
id: screenView
|
||||||
|
captureSource: focusedScreen.screen
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
SelectionRectangle {
|
||||||
|
id: selection
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property string position
|
||||||
|
property bool running: false
|
||||||
|
|
||||||
|
onAreaSelected: selection => {
|
||||||
|
let screen = focusedScreen.screen;
|
||||||
|
const x = Math.floor(selection.x) + screen.x;
|
||||||
|
const y = Math.floor(selection.y) + screen.y;
|
||||||
|
const width = Math.floor(selection.width);
|
||||||
|
const height = Math.floor(selection.height);
|
||||||
|
position = `${x},${y} ${width}x${height}`;
|
||||||
|
|
||||||
|
running = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
LazyLoader {
|
||||||
|
activeAsync: selection.running
|
||||||
|
|
||||||
|
Process {
|
||||||
|
id: grim
|
||||||
|
running: true
|
||||||
|
|
||||||
|
property var path: `${ShellSettings.settings.screenshotPath}/screenshot.png`
|
||||||
|
|
||||||
|
command: ["grim", "-g", selection.position, path]
|
||||||
|
|
||||||
|
|
||||||
|
onRunningChanged: {
|
||||||
|
if (!running) {
|
||||||
|
root.windowOpen = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stderr: SplitParser {
|
||||||
|
onRead: data => console.log(`line read: ${data}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function init() {
|
||||||
|
}
|
||||||
|
}
|
||||||
3
screencapture/Screenshot.qml
Normal file
3
screencapture/Screenshot.qml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
import QtQuick
|
||||||
|
|
||||||
|
Image {}
|
||||||
152
screencapture/SelectionCutout.qml
Normal file
152
screencapture/SelectionCutout.qml
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
import QtQuick
|
||||||
|
import ".."
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: root
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property color overlayColor: "#80000000"
|
||||||
|
property color borderColor: ShellSettings.colors["primary"]
|
||||||
|
property real borderWidth: 3
|
||||||
|
property real handleSize: 16
|
||||||
|
property var screen
|
||||||
|
|
||||||
|
property real centerX: width / 2
|
||||||
|
property real centerY: height / 2
|
||||||
|
property real minWidth: 400
|
||||||
|
property real minHeight: 300
|
||||||
|
|
||||||
|
// rect that holds positional data for the selection
|
||||||
|
property rect selectionRect: Qt.rect(centerX - minWidth / 2, centerY - minHeight / 2, minWidth, minHeight)
|
||||||
|
|
||||||
|
// handle positions
|
||||||
|
property point topLeftHandle: Qt.point(selectionRect.x, selectionRect.y)
|
||||||
|
property point topRightHandle: Qt.point(selectionRect.x + selectionRect.width, selectionRect.y)
|
||||||
|
property point bottomLeftHandle: Qt.point(selectionRect.x, selectionRect.y + selectionRect.height)
|
||||||
|
property point bottomRightHandle: Qt.point(selectionRect.x + selectionRect.width, selectionRect.y + selectionRect.height)
|
||||||
|
|
||||||
|
// dragging state
|
||||||
|
property int activeHandle: -1
|
||||||
|
property point dragStart: Qt.point(0, 0)
|
||||||
|
property rect initialRect: Qt.rect(0, 0, 0, 0)
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
var ctx = getContext("2d");
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// grey overlay
|
||||||
|
ctx.fillStyle = overlayColor;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// cut out the selection rectangle
|
||||||
|
ctx.globalCompositeOperation = "destination-out";
|
||||||
|
ctx.fillRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height);
|
||||||
|
ctx.globalCompositeOperation = "source-over";
|
||||||
|
|
||||||
|
// draw border
|
||||||
|
ctx.strokeStyle = borderColor;
|
||||||
|
ctx.lineWidth = borderWidth;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(topLeftHandle.x, topLeftHandle.y);
|
||||||
|
ctx.lineTo(topRightHandle.x, topRightHandle.y);
|
||||||
|
ctx.lineTo(bottomRightHandle.x, bottomRightHandle.y);
|
||||||
|
ctx.lineTo(bottomLeftHandle.x, bottomLeftHandle.y);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.stroke();
|
||||||
|
|
||||||
|
// draw handles
|
||||||
|
ctx.fillStyle = borderColor;
|
||||||
|
drawHandle(ctx, topLeftHandle);
|
||||||
|
drawHandle(ctx, topRightHandle);
|
||||||
|
drawHandle(ctx, bottomLeftHandle);
|
||||||
|
drawHandle(ctx, bottomRightHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawHandle(ctx, center) {
|
||||||
|
var radius = handleSize / 2;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI);
|
||||||
|
ctx.fill();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHandleAt(x, y) {
|
||||||
|
var halfSize = handleSize / 2;
|
||||||
|
var handles = [topLeftHandle, topRightHandle, bottomLeftHandle, bottomRightHandle];
|
||||||
|
|
||||||
|
for (var i = 0; i < handles.length; i++) {
|
||||||
|
var handle = handles[i];
|
||||||
|
if (x >= handle.x - halfSize && x <= handle.x + halfSize && y >= handle.y - halfSize && y <= handle.y + halfSize) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function constrainRect(rect) {
|
||||||
|
// Ensure minimum size
|
||||||
|
var width = Math.max(rect.width, minWidth);
|
||||||
|
var height = Math.max(rect.height, minHeight);
|
||||||
|
|
||||||
|
// Ensure within canvas bounds
|
||||||
|
var x = Math.max(0, Math.min(rect.x, root.width - width));
|
||||||
|
var y = Math.max(0, Math.min(rect.y, root.height - height));
|
||||||
|
|
||||||
|
return Qt.rect(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPressed: function (mouse) {
|
||||||
|
activeHandle = root.getHandleAt(mouse.x, mouse.y);
|
||||||
|
if (root.activeHandle >= 0) {
|
||||||
|
dragStart = Qt.point(mouse.x, mouse.y);
|
||||||
|
initialRect = root.selectionRect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// kinda stupid, should maybe bind a mouse area to each handle I don't know
|
||||||
|
onPositionChanged: function (mouse) {
|
||||||
|
if (root.activeHandle < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var dx = mouse.x - root.dragStart.x;
|
||||||
|
var dy = mouse.y - root.dragStart.y;
|
||||||
|
var newRect;
|
||||||
|
|
||||||
|
switch (root.activeHandle) {
|
||||||
|
// top left
|
||||||
|
case 0:
|
||||||
|
var newX = Math.max(0, Math.min(root.initialRect.x + dx, root.initialRect.x + root.initialRect.width - root.minWidth));
|
||||||
|
var newY = Math.max(0, Math.min(root.initialRect.y + dy, root.initialRect.y + root.initialRect.height - minHeight));
|
||||||
|
newRect = Qt.rect(newX, newY, root.initialRect.width - (newX - root.initialRect.x), root.initialRect.height - (newY - root.initialRect.y));
|
||||||
|
break;
|
||||||
|
// top right
|
||||||
|
case 1:
|
||||||
|
var newY = Math.max(0, Math.min(root.initialRect.y + dy, root.initialRect.y + root.initialRect.height - root.minHeight));
|
||||||
|
var newWidth = Math.max(root.minWidth, Math.min(root.initialRect.width + dx, root.width - root.initialRect.x));
|
||||||
|
newRect = Qt.rect(root.initialRect.x, newY, newWidth, root.initialRect.height - (newY - root.initialRect.y));
|
||||||
|
break;
|
||||||
|
// bottom left
|
||||||
|
case 2:
|
||||||
|
var newX = Math.max(0, Math.min(root.initialRect.x + dx, root.initialRect.x + root.initialRect.width - minWidth));
|
||||||
|
var newHeight = Math.max(root.minHeight, Math.min(root.initialRect.height + dy, root.height - root.initialRect.y));
|
||||||
|
newRect = Qt.rect(newX, root.initialRect.y, root.initialRect.width - (newX - root.initialRect.x), newHeight);
|
||||||
|
break;
|
||||||
|
// bottom right
|
||||||
|
case 3:
|
||||||
|
var newWidth = Math.max(root.minWidth, Math.min(root.initialRect.width + dx, root.width - root.initialRect.x));
|
||||||
|
var newHeight = Math.max(root.minHeight, Math.min(root.initialRect.height + dy, root.height - root.initialRect.y));
|
||||||
|
newRect = Qt.rect(root.initialRect.x, root.initialRect.y, newWidth, newHeight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionRect = root.constrainRect(newRect);
|
||||||
|
root.requestPaint();
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: {
|
||||||
|
root.activeHandle = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
screencapture/SelectionRectangle.qml
Normal file
54
screencapture/SelectionRectangle.qml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import QtQuick
|
||||||
|
import ".."
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property color overlayColor: "#80000000"
|
||||||
|
property color outlineColor: ShellSettings.colors["primary"]
|
||||||
|
property rect selectionRect
|
||||||
|
property point startPosition
|
||||||
|
signal areaSelected(rect selection)
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
var ctx = getContext("2d");
|
||||||
|
ctx.clearRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// grey overlay
|
||||||
|
ctx.fillStyle = overlayColor;
|
||||||
|
ctx.fillRect(0, 0, width, height);
|
||||||
|
|
||||||
|
// cut out the selection rectangle
|
||||||
|
ctx.globalCompositeOperation = "destination-out";
|
||||||
|
ctx.fillRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height);
|
||||||
|
ctx.globalCompositeOperation = "source-over";
|
||||||
|
ctx.strokeStyle = outlineColor;
|
||||||
|
ctx.lineWidth = 2;
|
||||||
|
ctx.strokeRect(selectionRect.x, selectionRect.y, selectionRect.width, selectionRect.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPressed: mouse => {
|
||||||
|
root.startPosition = Qt.point(mouse.x, mouse.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositionChanged: mouse => {
|
||||||
|
if (pressed) {
|
||||||
|
var x = Math.min(root.startPosition.x, mouse.x);
|
||||||
|
var y = Math.min(root.startPosition.y, mouse.y);
|
||||||
|
var width = Math.abs(mouse.x - root.startPosition.x);
|
||||||
|
var height = Math.abs(mouse.y - root.startPosition.y);
|
||||||
|
|
||||||
|
root.selectionRect = Qt.rect(x, y, width, height);
|
||||||
|
root.requestPaint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: mouse => {
|
||||||
|
root.visible = false;
|
||||||
|
root.areaSelected(root.selectionRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,9 +9,15 @@ import "volume-osd" as VolumeOSD
|
||||||
import "settings" as Settings
|
import "settings" as Settings
|
||||||
import "launcher" as Launcher
|
import "launcher" as Launcher
|
||||||
import "wallpaper" as Wallpaper
|
import "wallpaper" as Wallpaper
|
||||||
|
import "screencapture" as ScreenCapture
|
||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
Component.onCompleted: [Launcher.Controller.init(), Settings.Controller.init(), Notifications.NotificationCenter.init()]
|
Component.onCompleted: {
|
||||||
|
Launcher.Controller.init();
|
||||||
|
Settings.Controller.init();
|
||||||
|
Notifications.NotificationCenter.init();
|
||||||
|
ScreenCapture.Controller.init();
|
||||||
|
}
|
||||||
|
|
||||||
Variants {
|
Variants {
|
||||||
model: Quickshell.screens
|
model: Quickshell.screens
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import ".."
|
||||||
|
|
||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
required property var screen
|
|
||||||
property string matugenConf: Qt.resolvedUrl("matugen.toml").toString().replace("file://", "")
|
property string matugenConf: Qt.resolvedUrl("matugen.toml").toString().replace("file://", "")
|
||||||
|
|
||||||
LazyLoader {
|
LazyLoader {
|
||||||
|
|
@ -54,8 +53,18 @@ Scope {
|
||||||
id: matugen
|
id: matugen
|
||||||
running: false
|
running: false
|
||||||
|
|
||||||
// Formatter is keeping me hostage frfr...
|
// Don't format this lol
|
||||||
command: ["matugen", "image", ShellSettings.settings.wallpaperUrl.replace("file://", ""), "--type", ShellSettings.settings.colorScheme, "--json", "hex", "--config", root.matugenConf]
|
command: [
|
||||||
|
"matugen",
|
||||||
|
"image",
|
||||||
|
ShellSettings.settings.wallpaperUrl.replace("file://", ""),
|
||||||
|
"--type",
|
||||||
|
ShellSettings.settings.colorScheme,
|
||||||
|
"--json",
|
||||||
|
"hex",
|
||||||
|
"--config",
|
||||||
|
root.matugenConf
|
||||||
|
]
|
||||||
|
|
||||||
stdout: SplitParser {
|
stdout: SplitParser {
|
||||||
onRead: data => {
|
onRead: data => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue