// The GNOME 42 screenshot UI. // // Print freezes the screen, this overlay paints that frozen frame full-bleed // and puts the picker on top of it. Because the frame is a still, nothing under // the overlay animates while the user aims, and the overlay can never end up in // its own screenshot. // // The grab happens in services/Capture.qml *before* ShellState flips to // "capture", so by the time this window maps the backdrop is already on disk. import Quickshell import Quickshell.Wayland import QtQuick import qs.config import qs.services PanelWindow { id: win visible: ShellState.captureOpen color: "transparent" anchors { top: true bottom: true left: true right: true } // Fullscreen overlays must not reserve space, or every window on the // workspace gets resized as the picker opens and closes. exclusiveZone: 0 WlrLayershell.namespace: "qs-capture" // matched by a layerrule in hypr/rules.lua WlrLayershell.layer: WlrLayer.Overlay WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive onVisibleChanged: { if (!win.visible) return; // Hyprland reports window geometry in layout-global coordinates; the // picker works in window-local ones. On a single monitor this is 0,0. Capture.originX = win.screen ? win.screen.x : 0; Capture.originY = win.screen ? win.screen.y : 0; keys.forceActiveFocus(); } // Fire whatever the current mode has selected. function capture(): void { if (Capture.mode === "screen") { Capture.commit(null); return; } if (Capture.mode === "window") { const r = picker.selectedRect; if (r) Capture.commit(r); return; } if (!selection.hasSelection) return; const s = selection.selection; Capture.commit(Qt.rect(s.x + Capture.originX, s.y + Capture.originY, s.width, s.height)); } function cycleMode(step: int): void { const order = ["screen", "window", "selection"]; const i = order.indexOf(Capture.mode); Capture.mode = order[(i + step + order.length) % order.length]; } FocusScope { id: keys anchors.fill: parent focus: true Keys.onPressed: event => { switch (event.key) { case Qt.Key_Escape: Capture.close(); break; case Qt.Key_Return: case Qt.Key_Enter: case Qt.Key_Space: win.capture(); break; case Qt.Key_Left: win.cycleMode(-1); break; case Qt.Key_Right: win.cycleMode(1); break; case Qt.Key_1: Capture.mode = "screen"; break; case Qt.Key_2: Capture.mode = "window"; break; case Qt.Key_3: Capture.mode = "selection"; break; case Qt.Key_P: Capture.showPointer = !Capture.showPointer; break; default: return; } event.accepted = true; } // Painted under the frozen frame so a failed grab (no wlr-screencopy) // still gives the picker something opaque to sit on. Rectangle { anchors.fill: parent color: Theme.bgDark border.width: 0 } // The frozen screen. grim -o pins the grab to this output, so filling // the window is an exact 1:1 mapping; the source is 4500x3000 device // pixels drawn into 3000x2000 logical ones, i.e. full sharpness. Image { anchors.fill: parent source: Capture.freezeUrl fillMode: Image.Stretch // Unique filename per grab plus cache:false — otherwise QML would // happily repaint a previous session's frame. cache: false // Synchronous so the overlay never flashes black on the way in; a // raw PPM decode is a parse, not a decompress. asynchronous: false visible: status === Image.Ready } // ── Screen mode ───────────────────────────────────────────────────── // Nothing to pick, so just outline the output the way GNOME does. Frame { anchors.fill: parent visible: Capture.mode === "screen" strokeWidth: 3 strokeColor: Theme.accent } // ── Window mode ───────────────────────────────────────────────────── WindowPicker { id: picker anchors.fill: parent visible: Capture.mode === "window" originX: Capture.originX originY: Capture.originY onPicked: win.capture() } // ── Selection mode ────────────────────────────────────────────────── SelectionLayer { id: selection anchors.fill: parent visible: Capture.mode === "selection" onCommitted: win.capture() } CaptureBar { anchors { horizontalCenter: parent.horizontalCenter bottom: parent.bottom bottomMargin: 56 } captureEnabled: { if (Capture.mode === "window") return picker.selectedRect !== null; if (Capture.mode === "selection") return selection.hasSelection; return true; } onCaptureRequested: win.capture() } } }