feat: screenshot tool
This commit is contained in:
parent
b0a0b6d322
commit
53cea2beb3
3 changed files with 326 additions and 0 deletions
70
modules/areapicker/AreaPicker.qml
Normal file
70
modules/areapicker/AreaPicker.qml
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
import "root:/widgets"
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
LazyLoader {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property bool freeze
|
||||||
|
|
||||||
|
Variants {
|
||||||
|
model: Quickshell.screens
|
||||||
|
|
||||||
|
StyledWindow {
|
||||||
|
id: win
|
||||||
|
|
||||||
|
required property ShellScreen modelData
|
||||||
|
|
||||||
|
screen: modelData
|
||||||
|
name: "area-picker"
|
||||||
|
WlrLayershell.exclusionMode: ExclusionMode.Ignore
|
||||||
|
WlrLayershell.layer: WlrLayer.Overlay
|
||||||
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive
|
||||||
|
|
||||||
|
anchors.top: true
|
||||||
|
anchors.bottom: true
|
||||||
|
anchors.left: true
|
||||||
|
anchors.right: true
|
||||||
|
|
||||||
|
Picker {
|
||||||
|
loader: root
|
||||||
|
screen: win.modelData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IpcHandler {
|
||||||
|
target: "picker"
|
||||||
|
|
||||||
|
function open(): void {
|
||||||
|
root.freeze = false;
|
||||||
|
root.activeAsync = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openFreeze(): void {
|
||||||
|
root.freeze = true;
|
||||||
|
root.activeAsync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomShortcut {
|
||||||
|
name: "screenshot"
|
||||||
|
description: "Open screenshot tool"
|
||||||
|
onPressed: {
|
||||||
|
root.freeze = false;
|
||||||
|
root.activeAsync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomShortcut {
|
||||||
|
name: "screenshotFreeze"
|
||||||
|
description: "Open screenshot tool (freeze mode)"
|
||||||
|
onPressed: {
|
||||||
|
root.freeze = true;
|
||||||
|
root.activeAsync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
254
modules/areapicker/Picker.qml
Normal file
254
modules/areapicker/Picker.qml
Normal file
|
|
@ -0,0 +1,254 @@
|
||||||
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
|
import "root:/widgets"
|
||||||
|
import "root:/services"
|
||||||
|
import "root:/config"
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Effects
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property LazyLoader loader
|
||||||
|
required property ShellScreen screen
|
||||||
|
|
||||||
|
property int borderWidth
|
||||||
|
property real rounding
|
||||||
|
|
||||||
|
property real ssx
|
||||||
|
property real ssy
|
||||||
|
|
||||||
|
property real sx: 0
|
||||||
|
property real sy: 0
|
||||||
|
property real ex: screen.width
|
||||||
|
property real ey: screen.height
|
||||||
|
|
||||||
|
property real rsx: Math.min(sx, ex)
|
||||||
|
property real rsy: Math.min(sy, ey)
|
||||||
|
property real sw: Math.abs(sx - ex)
|
||||||
|
property real sh: Math.abs(sy - ey)
|
||||||
|
|
||||||
|
property list<var> clients: Hyprland.clients.filter(c => c.workspace.id === Hyprland.activeWsId)
|
||||||
|
|
||||||
|
function checkClientRects(x: real, y: real): void {
|
||||||
|
for (const c of clients) {
|
||||||
|
if (c.x <= x && c.y <= y && c.x + c.width >= x && c.y + c.height >= y) {
|
||||||
|
sx = c.x;
|
||||||
|
sy = c.y;
|
||||||
|
ex = c.x + c.width;
|
||||||
|
ey = c.y + c.height;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
opacity: 0
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
// Break binding if frozen
|
||||||
|
if (loader.freeze)
|
||||||
|
clients = clients;
|
||||||
|
|
||||||
|
opacity = 1;
|
||||||
|
sx = screen.width / 2 - 100;
|
||||||
|
sy = screen.height / 2 - 100;
|
||||||
|
ex = screen.width / 2 + 100;
|
||||||
|
ey = screen.height / 2 + 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressed: event => {
|
||||||
|
ssx = event.x;
|
||||||
|
ssy = event.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: {
|
||||||
|
if (closeAnim.running)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Quickshell.execDetached(["sh", "-c", `grim -l 0 -g '${Math.ceil(rsx)},${Math.ceil(rsy)} ${Math.floor(sw)}x${Math.floor(sh)}' - | swappy -f -`]);
|
||||||
|
closeAnim.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPositionChanged: event => {
|
||||||
|
const x = event.x;
|
||||||
|
const y = event.y;
|
||||||
|
|
||||||
|
if (pressed) {
|
||||||
|
sx = ssx;
|
||||||
|
sy = ssy;
|
||||||
|
ex = x;
|
||||||
|
ey = y;
|
||||||
|
} else {
|
||||||
|
checkClientRects(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
focus: true
|
||||||
|
Keys.onEscapePressed: closeAnim.start()
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: closeAnim
|
||||||
|
|
||||||
|
ParallelAnimation {
|
||||||
|
Anim {
|
||||||
|
target: root
|
||||||
|
property: "opacity"
|
||||||
|
to: 0
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
}
|
||||||
|
Anim {
|
||||||
|
target: root
|
||||||
|
properties: "rsx,rsy"
|
||||||
|
to: 0
|
||||||
|
}
|
||||||
|
Anim {
|
||||||
|
target: root
|
||||||
|
property: "sw"
|
||||||
|
to: root.screen.width
|
||||||
|
}
|
||||||
|
Anim {
|
||||||
|
target: root
|
||||||
|
property: "sh"
|
||||||
|
to: root.screen.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PropertyAction {
|
||||||
|
target: root.loader
|
||||||
|
property: "activeAsync"
|
||||||
|
value: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: Hyprland
|
||||||
|
|
||||||
|
function onActiveWsIdChanged(): void {
|
||||||
|
root.checkClientRects(root.mouseX, root.mouseY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
running: true
|
||||||
|
command: ["hyprctl", "-j", "getoption", "general:border_size"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: root.borderWidth = JSON.parse(text).int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Process {
|
||||||
|
running: true
|
||||||
|
command: ["hyprctl", "-j", "getoption", "decoration:rounding"]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: root.rounding = JSON.parse(text).int
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
active: root.loader.freeze
|
||||||
|
asynchronous: true
|
||||||
|
|
||||||
|
sourceComponent: ScreencopyView {
|
||||||
|
captureSource: root.screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledRect {
|
||||||
|
id: background
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Colours.palette.m3secondaryContainer
|
||||||
|
visible: false
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: selectionWrapper
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
layer.enabled: true
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: selectionRect
|
||||||
|
|
||||||
|
radius: root.rounding
|
||||||
|
x: root.rsx
|
||||||
|
y: root.rsy
|
||||||
|
implicitWidth: root.sw
|
||||||
|
implicitHeight: root.sh
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: background
|
||||||
|
maskSource: selectionWrapper
|
||||||
|
maskEnabled: true
|
||||||
|
maskInverted: true
|
||||||
|
maskSpreadAtMin: 1
|
||||||
|
maskThresholdMin: 0.5
|
||||||
|
opacity: 0.3
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: "transparent"
|
||||||
|
radius: root.rounding > 0 ? root.rounding + root.borderWidth : 0
|
||||||
|
border.width: root.borderWidth
|
||||||
|
border.color: Colours.palette.m3primary
|
||||||
|
|
||||||
|
x: selectionRect.x - root.borderWidth
|
||||||
|
y: selectionRect.y - root.borderWidth
|
||||||
|
implicitWidth: selectionRect.implicitWidth + root.borderWidth * 2
|
||||||
|
implicitHeight: selectionRect.implicitHeight + root.borderWidth * 2
|
||||||
|
|
||||||
|
Behavior on border.color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Appearance.anim.durations.normal
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.standard
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
Anim {
|
||||||
|
duration: Appearance.anim.durations.large
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on rsx {
|
||||||
|
enabled: !root.pressed
|
||||||
|
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on rsy {
|
||||||
|
enabled: !root.pressed
|
||||||
|
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on sw {
|
||||||
|
enabled: !root.pressed
|
||||||
|
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on sh {
|
||||||
|
enabled: !root.pressed
|
||||||
|
|
||||||
|
Anim {}
|
||||||
|
}
|
||||||
|
|
||||||
|
component Anim: NumberAnimation {
|
||||||
|
duration: Appearance.anim.durations.expressiveDefaultSpatial
|
||||||
|
easing.type: Easing.BezierSpline
|
||||||
|
easing.bezierCurve: Appearance.anim.curves.expressiveDefaultSpatial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
import "modules"
|
import "modules"
|
||||||
import "modules/drawers"
|
import "modules/drawers"
|
||||||
import "modules/background"
|
import "modules/background"
|
||||||
|
import "modules/areapicker"
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|
||||||
ShellRoot {
|
ShellRoot {
|
||||||
Background {}
|
Background {}
|
||||||
Drawers {}
|
Drawers {}
|
||||||
|
AreaPicker {}
|
||||||
|
|
||||||
Shortcuts {}
|
Shortcuts {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue