repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/Screenshots.qml
Rawimport QtQuick
Item {
id: root
property Niri niriService
property Osd osdService
property string lastColor: ""
property var recent: []
property var pending: []
property int requestSerial: 0
property var lastAction: ({})
property var selection: ({
"model": "normalized-cross-screen-rect",
"phase": "idle",
"press": {
"x": 0,
"y": 0
},
"current": {
"x": 0,
"y": 0
},
"rect": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
"cursor": "default",
"completionFlash": false
})
property var lastOsd: ({})
readonly property bool nativeActionRoute: !!niriService && !!niriService.available
readonly property var nativeActions: ["screenshot-screen", "screenshot-window", "screenshot", "pick-color"]
readonly property var boundedActions: ["focused-screen", "focused-window", "interactive", "select-region", "pick-color", "copy", "save", "open-record", "clear"]
readonly property var toolbarActions: ["copy", "save", "cancel", "color"]
function rectFromPoints(start, end) {
const x = Math.min(start.x, end.x);
const y = Math.min(start.y, end.y);
return {
"x": x,
"y": y,
"width": Math.abs(end.x - start.x),
"height": Math.abs(end.y - start.y)
};
}
function record(id, kind, path) {
const next = [
{
"id": id,
"kind": kind,
"path": path,
"source": "niri-native",
"saved": true
}
];
for (let i = 0; i < recent.length; i++) {
if (recent[i].id !== id)
next.push(recent[i]);
}
recent = next.slice(0, 12);
}
// Niri 26.04, revision 8ed0da44d974c32c6877d2f4630c314da0717ecb:
// https://github.com/niri-wm/niri/blob/8ed0da44d974c32c6877d2f4630c314da0717ecb/src/niri.rs#L5643-L5745
// Only a non-null completion path proves a save. Null covers clipboard-only,
// failed writes, and paths not representable as UTF-8. Encoding/render failures
// and native cancellation emit no completion. Submission is not save success.
// There is no request ID or cancellation event. Pending means unconfirmed,
// not necessarily still running, and clear() forgets it without cancelling Niri.
// Exact paths are the only correlation: same-path requests consume one match
// FIFO, but concurrent/external saves to that path cannot be distinguished.
function acceptCaptured(path) {
if (typeof path !== "string" || path.length === 0)
return;
const index = pending.findIndex(request => request.path === path);
if (index < 0)
return;
const request = pending[index];
pending = pending.filter((_, i) => i !== index);
record(request.id, request.kind, path);
if (lastAction.id === request.id) {
lastAction = Object.assign({}, lastAction, {
"status": "saved",
"saved": true,
"completed": true
});
}
if (selection.requestId === request.id) {
selection = Object.assign({}, selection, {
"phase": "completed",
"cursor": "default",
"completionFlash": true
});
}
}
function cornerHandles(rect) {
const size = rect.width > 0 && rect.height > 0 ? 14 : 0;
return [
{
"id": "top-left",
"x": rect.x,
"y": rect.y,
"size": size,
"role": "ui.accent"
},
{
"id": "top-right",
"x": rect.x + rect.width,
"y": rect.y,
"size": size,
"role": "ui.accent"
},
{
"id": "bottom-left",
"x": rect.x,
"y": rect.y + rect.height,
"size": size,
"role": "ui.accent"
},
{
"id": "bottom-right",
"x": rect.x + rect.width,
"y": rect.y + rect.height,
"size": size,
"role": "ui.accent"
}
];
}
function selectionVisuals(selectionState) {
const rect = selectionState.rect || {
"x": 0,
"y": 0,
"width": 0,
"height": 0
};
const active = selectionState.phase === "dragging" || selectionState.phase === "pending" || selectionState.phase === "completed";
return {
"source": "Nuguland screenSnip plus Nuguland Geom.qml",
"parts": ["dimmed-mask", "clear-cutout", "corner-handles", "dimensions", "toolbar-actions"],
"dimmedMask": {
"kind": "dimmed-mask",
"visible": active,
"opacity": active ? 0.42 : 0,
"role": "ui.backdrop"
},
"clearCutout": {
"kind": "clear-cutout",
"rect": rect
},
"cornerHandles": {
"kind": "corner-handles",
"handles": cornerHandles(rect)
},
"dimensions": {
"kind": "dimensions",
"text": `${Math.round(rect.width)}×${Math.round(rect.height)}`,
"width": Math.round(rect.width),
"height": Math.round(rect.height)
},
"toolbarActions": {
"kind": "toolbar-actions",
"actions": toolbarActions,
"anchor": rect.height >= 48 ? "selection-top" : "screen-top"
},
"nugulandCornerAccents": {
"bounded": true,
"freePhysicsLoop": false,
"anchors": cornerHandles(rect).map(handle => {
return ({
"id": handle.id,
"x": handle.x,
"y": handle.y
});
})
}
};
}
function screenshotsPanelContract() {
return {
"source": "Nuguland AreaPicker.qml plus Nuguland RegionSelector.qml WRegionSelectionPanel.qml",
"ownedBy": "PopoutHost.qml inline ScreenshotsPanel",
"niriCapturePanel": true,
"captureActionGrid": true,
"regionSelectionPreview": true,
"recentCaptureRows": true,
"colorPickSwatch": true,
"toolbarActionRail": true,
"boundedCornerAccents": true,
"serviceActionsOnly": true,
"captureBackend": "niri-native",
"panelActions": ["focused-screen", "focused-window", "select-region", "pick-color"],
"selectionParts": ["dimmed-mask", "clear-cutout", "corner-handles", "dimensions", "toolbar-actions"]
};
}
function currentState() {
return {
"backend": "Screenshots.qml",
"usesNiriNative": true,
"nativeActions": nativeActions,
"nativeActionRoute": nativeActionRoute,
"helperProcesses": [],
"serviceOwnedActions": true,
"boundedActions": boundedActions,
"screenshotsPanelContract": screenshotsPanelContract(),
"selection": selection,
"selectionVisuals": selectionVisuals(selection),
"recent": recent,
"recentCount": recent.length,
"pending": pending,
"pendingCount": pending.length,
"lastColor": lastColor,
"lastAction": lastAction,
"osd": lastOsd
};
}
function stateJson() {
return JSON.stringify(currentState());
}
function beginSelection(x, y) {
const point = {
"x": x,
"y": y
};
selection = {
"model": "normalized-cross-screen-rect",
"phase": "dragging",
"press": point,
"current": point,
"rect": {
"x": x,
"y": y,
"width": 0,
"height": 0
},
"cursor": "crosshair",
"completionFlash": false
};
lastAction = {
"kind": "select-region-begin",
"point": point
};
return stateJson();
}
function updateSelection(x, y) {
const start = selection.press || {
"x": 0,
"y": 0
};
const current = {
"x": x,
"y": y
};
selection = Object.assign({}, selection, {
"phase": "dragging",
"current": current,
"rect": rectFromPoints(start, current),
"cursor": "crosshair",
"completionFlash": false,
"requestId": null
});
lastAction = {
"kind": "select-region-update",
"point": current
};
return stateJson();
}
function completeSelection(path) {
return capture("select-region", path);
}
function cancelSelection() {
selection = Object.assign({}, selection, {
"phase": "idle",
"cursor": "default",
"completionFlash": false,
"requestId": null
});
// This only dismisses our preview, not Niri's native selection UI.
lastAction = {
"kind": "cancel"
};
return stateJson();
}
function capture(kind, path) {
const actionKind = String(kind);
const target = typeof path === "string" ? path : "";
const interactive = actionKind === "interactive" || actionKind === "select-region";
const nativeAction = actionKind === "focused-screen" ? "screenshot-screen" : actionKind === "focused-window" ? "screenshot-window" : interactive ? "screenshot" : "";
const status = !nativeAction ? "unsupported" : !target ? "invalid-path" : !nativeActionRoute ? "unavailable" : "pending";
// These IDs belong to local history only; Niri never receives them.
const id = status === "pending" ? `capture-${actionKind}-${++requestSerial}` : null;
lastAction = {
"id": id,
"kind": actionKind,
"nativeAction": nativeAction,
"path": target,
"nativeActionRoute": status === "pending",
"interactive": interactive,
"status": status,
"saved": false,
"completed": false
};
if (actionKind === "select-region") {
selection = Object.assign({}, selection, {
"phase": status === "pending" ? "pending" : "idle",
"cursor": "default",
"completionFlash": false,
"requestId": id
});
}
if (status === "pending") {
pending = pending.concat([lastAction]);
if (actionKind === "focused-screen")
niriService.screenshotScreen(target);
else if (actionKind === "focused-window")
niriService.screenshotWindow(target);
else
niriService.screenshot(target);
}
return stateJson();
}
function acceptPickedColor(color) {
const value = String(color || "").toLowerCase();
if (!/^#[0-9a-f]{6}$/.test(value))
return;
lastColor = value;
lastAction = Object.assign({}, lastAction, {
"color": lastColor,
"completed": true
});
if (osdService) {
try {
lastOsd = JSON.parse(osdService.trigger("color", lastColor));
} catch (error) {
lastOsd = {
"event": "color",
"value": lastColor
};
}
} else {
lastOsd = {
"event": "color",
"value": lastColor
};
}
}
function pickColor() {
lastColor = "";
if (nativeActionRoute)
niriService.pickColor();
lastAction = {
"kind": "pick-color",
"nativeAction": "pick-color",
"color": lastColor,
"nativeActionRoute": nativeActionRoute,
"interactive": true,
"completed": false
};
lastOsd = {
"event": "color",
"value": "picking"
};
return stateJson();
}
function recordAction(kind, id) {
lastAction = {
"kind": String(kind),
"id": String(id),
"nativeActionRoute": false
};
return stateJson();
}
function clear() {
recent = [];
pending = [];
if (selection.phase === "pending")
cancelSelection();
lastAction = {
"kind": "clear"
};
return stateJson();
}
Connections {
function onScreenshotCaptured(path) {
root.acceptCaptured(path);
}
function onLastPickedColorChanged() {
root.acceptPickedColor(root.niriService.lastPickedColor);
}
target: root.niriService
}
}