Build the Panama Hyprland desktop
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user