diff --git a/flake.lock b/flake.lock index c2ae145..af54a74 100644 --- a/flake.lock +++ b/flake.lock @@ -384,11 +384,11 @@ ] }, "locked": { - "lastModified": 1711618169, - "narHash": "sha256-K5j+SlsGfyrJeBzcZVH02O8P4T+ep4UEFryVVBWFdAw=", + "lastModified": 1711716099, + "narHash": "sha256-j9j3O4iR0ovVZTgjUb2nyvyZ1fPNMrR0fm65E4kZcgo=", "ref": "refs/heads/master", - "rev": "439788fce09efff383768ac2403526f9e0fe5ddd", - "revCount": 136, + "rev": "83afce7f68f8ffc74f53642d2bac9e604bd64dfc", + "revCount": 137, "type": "git", "url": "https://git.outfoxxed.me/outfoxxed/quickshell" }, diff --git a/modules/hyprland/hyprland.conf b/modules/hyprland/hyprland.conf index b9b3792..ba19a12 100644 --- a/modules/hyprland/hyprland.conf +++ b/modules/hyprland/hyprland.conf @@ -109,6 +109,8 @@ layerrule = animation fade, shell:background layerrule = blur, shell:bar layerrule = ignorezero, shell:bar +layerrule = noanim, shell:screenshot + layerrule = blur, walker layerrule = ignorezero, walker layerrule = animation popin 90%, walker @@ -136,8 +138,10 @@ bind = $mod+SHIFT, return, exec, alacritty --class AlacrittyFloating bind = $mod, grave, exec, $launcher bind = $mod+SHIFT, q, hy3:killactive +bind = $mod+SHIFT, s, exec, echo "screenshot" | nc -w 0 -U /run/user/1000/quickshell.sock bind = $mod, c, exec, echo "termselect:start" | nc -w 0 -U /run/user/1000/quickshell.sock bindr = $mod, c, exec, echo "termselect:stop" | nc -w 0 -U /run/user/1000/quickshell.sock +bind = $mod, PERIOD, exec, quickshell -c lockscreen bind = $mod, f, fullscreen, 1 bind = $mod+SHIFT, f, fullscreen, 0 @@ -163,10 +167,6 @@ bind = ,XF86AudioStop, exec, playerctl -a stop bind = ,XF86AudioNext, exec, playerctl next bind = ,XF86AudioPrev, exec, playerctl previous -bind = $mod+SHIFT, s, exec, grim -g "$(slurp)" - | wl-copy - -bind = $mod, PERIOD, exec, quickshell -c lockscreen - bind = $mod, h, hy3:movefocus, l bind = $mod, j, hy3:movefocus, d bind = $mod, k, hy3:movefocus, u diff --git a/modules/user/modules/quickshell/default.nix b/modules/user/modules/quickshell/default.nix index 9b910fa..5ff1263 100644 --- a/modules/user/modules/quickshell/default.nix +++ b/modules/user/modules/quickshell/default.nix @@ -5,6 +5,7 @@ in { qt6.qtimageformats # amog quickshell.packages.${system}.default pamtester # lockscreen + grim imagemagick # screenshot ]; xdg.configFile."quickshell/manifest.conf".text = lib.generators.toKeyValue {} { diff --git a/modules/user/modules/quickshell/shell/OverlayWidget.qml b/modules/user/modules/quickshell/shell/OverlayWidget.qml index 4910f66..7da1a18 100644 --- a/modules/user/modules/quickshell/shell/OverlayWidget.qml +++ b/modules/user/modules/quickshell/shell/OverlayWidget.qml @@ -40,7 +40,6 @@ Item { yCurve.interpolate(p, collapsedLayerRect.y, expandedLayerRect.y), xCurve.interpolate(p, collapsedLayerRect.width, expandedLayerRect.width), yCurve.interpolate(p, collapsedLayerRect.height, expandedLayerRect.height), - ); } diff --git a/modules/user/modules/quickshell/shell/ShellGlobals.qml b/modules/user/modules/quickshell/shell/ShellGlobals.qml index cc969f8..c4e047e 100644 --- a/modules/user/modules/quickshell/shell/ShellGlobals.qml +++ b/modules/user/modules/quickshell/shell/ShellGlobals.qml @@ -4,6 +4,8 @@ import QtQuick import Quickshell Singleton { + readonly property string rtpath: "/run/user/1000/quickshell" + readonly property var colors: QtObject { readonly property var bar: "#30c0ffff"; readonly property var barOutline: "#50ffffff"; diff --git a/modules/user/modules/quickshell/shell/ShellIpc.qml b/modules/user/modules/quickshell/shell/ShellIpc.qml index 3e796a4..6d8f7de 100644 --- a/modules/user/modules/quickshell/shell/ShellIpc.qml +++ b/modules/user/modules/quickshell/shell/ShellIpc.qml @@ -5,6 +5,7 @@ import Quickshell.Io Singleton { property bool termSelect: false; + signal screenshot(); SocketServer { active: true @@ -15,6 +16,9 @@ Singleton { onRead: message => { console.log(message) switch (message) { + case "screenshot": + screenshot(); + break; case "termselect:start": termSelect = true; break; diff --git a/modules/user/modules/quickshell/shell/screenshot/Controller.qml b/modules/user/modules/quickshell/shell/screenshot/Controller.qml new file mode 100644 index 0000000..ff000c1 --- /dev/null +++ b/modules/user/modules/quickshell/shell/screenshot/Controller.qml @@ -0,0 +1,206 @@ +// very bad code DO NOT COPY + +import QtQuick +import Quickshell +import Quickshell.Io +import Quickshell.Wayland +import ".." + +Scope { + id: root + property bool shooting: false; + property bool shootingComplete: false; + property bool visible: false; + readonly property string path: `${ShellGlobals.rtpath}/screenshot.png`; + + onShootingChanged: { + if (shooting) { + grimProc.running = true + } else { + visible = false + shootingComplete = false + cleanupProc.running = true + } + } + + Process { + id: grimProc + command: ["grim", "-l", "0", path] + onExited: code => { + if (code == 0) { + root.visible = true + } else { + console.log("screenshot failed") + cleanupProc.running = true + } + } + } + + Process { + id: magickProc + command: [ + "magick", + path, + "-crop", + `${selection.w}x${selection.h}+${selection.x}+${selection.y}`, + "-quality", + "70", + path, + ] + + onExited: wlCopy.running = true; + } + + Process { + id: wlCopy + command: ["sh", "-c", `wl-copy < '${path}'`] + + onExited: shootingComplete = true; + } + + Process { + id: cleanupProc + command: ["rm", path] + } + + QtObject { + id: selection + property real x1; + property real y1; + property real x2; + property real y2; + + readonly property real x: Math.min(x1, x2) + readonly property real y: Math.min(y1, y2) + readonly property real w: Math.max(x1, x2) - x + readonly property real h: Math.max(y1, y2) - y + } + + LazyLoader { + loading: root.shooting + active: root.visible + onLoadingChanged: console.log(`loading set to ${loading} at ${new Date()}`) + onActiveChanged: console.log(`active set to ${active} at ${new Date()}`) + + Variants { + model: Quickshell.screens + + property bool selectionComplete: false + + Component.onCompleted: { + selection.x1 = 0 + selection.y1 = 0 + selection.x2 = 0 + selection.y2 = 0 + } + + PanelWindow { + required property var modelData; + screen: modelData + visible: root.visible + exclusionMode: ExclusionMode.Ignore + WlrLayershell.namespace: "shell:screenshot" + WlrLayershell.layer: WlrLayer.Overlay + + anchors { + top: true + left: true + right: true + bottom: true + } + + MouseArea { + id: area + anchors.fill: parent + cursorShape: selectionComplete ? Qt.WaitCursor : Qt.CrossCursor + enabled: !selectionComplete + + onPressed: { + selection.x1 = mouseX + screen.x; + selection.x2 = selection.x1; + selection.y1 = mouseY + screen.y; + selection.y2 = selection.y1; + } + + onPositionChanged: { + selection.x2 = mouseX + screen.x; + selection.y2 = mouseY + screen.y; + } + + onReleased: { + console.log(`area: ${selection.x} ${selection.y} ${selection.w} ${selection.h}`) + + if (selection.w > 0 && selection.h > 0) { + magickProc.running = true + selectionComplete = true + } else { + root.shooting = false + } + } + + Image { + parent: area + anchors.fill: parent + source: root.visible ? root.path : "" + sourceClipRect: Qt.rect(screen.x, screen.y, screen.width, screen.height) + } + + CutoutRect { + id: cutoutRect + anchors.fill: parent + innerX: selection.x - screen.x + innerY: selection.y - screen.y + innerW: selection.w + innerH: selection.h + + NumberAnimation { + id: rectFlashIn + target: cutoutRect + property: "opacity" + duration: 200 + easing.type: Easing.OutExpo + from: 0.0 + to: 1.0 + } + + PropertyAnimation { + running: selectionComplete + target: cutoutRect + property: "innerBorderColor" + duration: 200 + to: "#00ff20" + } + + NumberAnimation { + running: selectionComplete + target: cutoutRect + property: "backgroundOpacity" + duration: 200 + to: 0.0 + } + + NumberAnimation { + running: shootingComplete + target: cutoutRect + property: "opacity" + easing.type: Easing.OutCubic + duration: 150 + to: 0.0 + onStopped: root.shooting = false + } + } + + Connections { + target: root + + function onVisibleChanged() { + if (root.visible) { + rectFlashIn.start(); + } + } + } + } + } + } + } +} diff --git a/modules/user/modules/quickshell/shell/screenshot/CutoutRect.qml b/modules/user/modules/quickshell/shell/screenshot/CutoutRect.qml new file mode 100644 index 0000000..47cc27f --- /dev/null +++ b/modules/user/modules/quickshell/shell/screenshot/CutoutRect.qml @@ -0,0 +1,67 @@ +import QtQuick + +Item { + id: root + property color backgroundColor: "#20ffffff" + property real backgroundOpacity: 1.0 + property alias innerBorderColor: center.border.color + property alias innerX: center.x + property alias innerY: center.y + property alias innerW: center.width + property alias innerH: center.height + + Rectangle { + id: center + border.color: "white" + border.width: 2 + color: "transparent" + } + + Rectangle { + color: root.backgroundColor + opacity: backgroundOpacity + + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: center.top + } + } + + Rectangle { + color: root.backgroundColor + opacity: backgroundOpacity + + anchors { + top: center.bottom + left: parent.left + right: parent.right + bottom: parent.bottom + } + } + + Rectangle { + color: root.backgroundColor + opacity: backgroundOpacity + + anchors { + top: center.top + left: parent.left + right: center.left + bottom: center.bottom + } + } + + Rectangle { + color: root.backgroundColor + opacity: backgroundOpacity + + anchors { + top: center.top + left: center.right + right: parent.right + bottom: center.bottom + } + } +} diff --git a/modules/user/modules/quickshell/shell/shell.qml b/modules/user/modules/quickshell/shell/shell.qml index 2898ed0..9c1c423 100644 --- a/modules/user/modules/quickshell/shell/shell.qml +++ b/modules/user/modules/quickshell/shell/shell.qml @@ -4,8 +4,30 @@ import Quickshell.Wayland import QtQuick import QtQuick.Layouts import ".." +import "screenshot" as Screenshot ShellRoot { + Process { + command: ["mkdir", "-p", ShellGlobals.rtpath] + running: true + } + + LazyLoader { + id: screenshot + loading: true + + Screenshot.Controller { + } + } + + Connections { + target: ShellIpc + + function onScreenshot() { + screenshot.item.shooting = true; + } + } + Variants { model: Quickshell.screens