repositories / dotfiles
dotfiles
bugabingas dorkfiles
owned by admin
quickshell/nuguland/Clipboard.qml
Rawimport QtQuick
import Quickshell
import Quickshell.Io
import "clip.js" as Clip
Item {
id: root
property SurfaceManager surfaces
property Niri niriService
property string pendingPasteId: ""
property var pasteWindowId: null
property int pasteAttempts: 0
property string focusReply: ""
property bool privateMode: false
property bool vaultCopyActive: false
property bool vaultPauseAcknowledged: false
property var rows: []
signal vaultCapturePaused
property var lastAction: ({})
property bool persistenceReady: false
readonly property int maxRows: 40
readonly property string historyPath: Quickshell.statePath("clipboard-history.json")
// --- linear lookup over the raw persisted rows (id, title, body, pinned, createdAt) ---
function rawRowById(id) {
const needle = String(id);
for (let i = 0; i < rows.length; i++)
if (rows[i].id === needle) {
return rows[i];
}
return null;
}
function captureClipboard(text) {
const body = String(text || "");
if (privateMode || !Clip.isReadableText(body))
return;
const id = `clip-${Clip.hashText(body)}`;
const next = [];
let existingPin = false;
for (let i = 0; i < rows.length; i++) {
if (rows[i].id === id) {
existingPin = !!rows[i].pinned;
continue;
}
next.push(rows[i]);
}
next.unshift({
"id": id,
"title": Clip.rowTitle(body),
"body": body,
"pinned": existingPin,
"createdAt": Date.now()
});
rows = next.slice(0, maxRows);
saveHistory();
}
// --- trimmed serialization: the view binds the cloned rows directly ---
function stateObject(query) {
const now = Date.now();
const viewRows = Clip.filterRows(rows, query).map(row => {
return Clip.cloneRow(row, now);
});
return {
"privateMode": privateMode,
"privateRole": privateMode ? "warning" : "ui.accent",
"rows": viewRows,
"lastAction": lastAction
};
}
function stateJson() {
return JSON.stringify(stateObject(""));
}
function search(query) {
return JSON.stringify(stateObject(query));
}
function launcherRows(query) {
const now = Date.now();
const result = Clip.filterRows(rows, query).map(row => {
return Clip.cloneRow(row, now);
});
for (let i = 0; i < result.length; i++) {
result[i].subtitle = privateMode ? "private clipboard" : "clipboard";
result[i].keyboard = true;
}
return JSON.stringify(result);
}
function mutateRow(id, callback) {
const next = [];
for (let i = 0; i < rows.length; i++) {
const row = Object.assign({}, rows[i]);
if (row.id === String(id))
callback(row);
next.push(row);
}
rows = next;
saveHistory();
}
function togglePin(id) {
mutateRow(id, row => {
return row.pinned = !row.pinned;
});
lastAction = {
"kind": "pin",
"id": String(id)
};
return stateJson();
}
function copy(id) {
const row = rawRowById(id);
if (row)
Quickshell.execDetached(["wl-copy", String(row.body || "")]);
lastAction = {
"kind": "copy",
"id": String(id),
"accepted": !!row
};
return stateJson();
}
function paste(id) {
if (pendingPasteId.length > 0)
return stateJson();
const target = surfaces && surfaces.popoutOpen ? surfaces.returnWindowId : (niriService ? niriService.focusedWindow : null);
const accepted = !!rawRowById(id) && target !== null && target !== undefined;
lastAction = {
"kind": "paste",
"id": String(id),
"accepted": accepted,
"pending": accepted,
"error": accepted ? "" : "no paste destination",
"route": "clipboard-target-typing",
"pasteInputRoute": "wtype"
};
if (!accepted)
return stateJson();
pendingPasteId = String(id);
pasteWindowId = target;
pasteAttempts = 0;
if (surfaces)
surfaces.closePopout("clipboard-paste");
// Wait for observable compositor focus, not a guessed typing delay.
pasteFocusTimer.start();
return stateJson();
}
function finishPaste(error) {
lastAction = Object.assign({}, lastAction, {
"pending": false,
"error": error
});
pendingPasteId = "";
pasteWindowId = null;
focusReply = "";
pasteFocusTimer.stop();
}
function checkPasteFocus(exitCode) {
if (pendingPasteId.length === 0)
return;
if (surfaces && surfaces.popoutOpen) {
finishPaste("paste cancelled: another panel opened");
return;
}
let focused = null;
try {
focused = JSON.parse(focusReply);
} catch (_) {}
if (exitCode === 0 && focused && focused.id === pasteWindowId) {
const row = rawRowById(pendingPasteId);
if (row)
Quickshell.execDetached(["/usr/bin/wtype", "--", String(row.body || "")]);
finishPaste(row ? "" : "clipboard item no longer available");
} else if (++pasteAttempts >= 40 || (focused && focused.id !== pasteWindowId)) {
finishPaste("paste destination lost focus");
} else {
pasteFocusTimer.restart();
}
}
function deleteRow(id) {
const target = String(id);
const next = [];
for (let i = 0; i < rows.length; i++) {
if (rows[i].id !== target)
next.push(rows[i]);
}
rows = next;
saveHistory();
lastAction = {
"kind": "delete",
"id": target
};
return stateJson();
}
function loadHistoryText(text) {
try {
const parsed = JSON.parse(String(text || "{}"));
const loadedRows = Array.isArray(parsed.rows) ? parsed.rows : [];
rows = loadedRows.map(row => {
return Clip.persistedRow(row);
}).filter(row => {
return row.id.length > 0 && Clip.isReadableText(row.body);
}).slice(0, maxRows);
} catch (error) {
rows = [];
}
persistenceReady = true;
saveHistory();
}
function saveHistory() {
if (!persistenceReady)
return;
const payload = {
"version": 1,
"rows": rows.map(row => {
return Clip.persistedRow(row);
}).slice(0, maxRows)
};
clipboardHistoryFile.setText(JSON.stringify(payload, null, 2));
}
function setPrivateMode(enabled) {
privateMode = enabled;
lastAction = {
"kind": "private",
"enabled": enabled
};
return stateJson();
}
function pauseForVaultCopy(timeoutSeconds) {
vaultPauseAcknowledged = false;
vaultCopyActive = true;
vaultCopyFailsafe.interval = (Math.max(1, Number(timeoutSeconds || 30)) + 2) * 1000;
vaultCopyFailsafe.restart();
if (!clipboardWatcher.running) {
vaultPauseAcknowledged = true;
vaultCapturePaused();
}
}
function resumeAfterVaultCopy() {
vaultCopyFailsafe.stop();
vaultCopyActive = false;
vaultPauseAcknowledged = false;
}
// --- exercises clip.js detect/sort/filter/clone through the service ---
function selfTest() {
const cases = [];
function ok(name, cond) {
cases.push({
"name": name,
"pass": !!cond
});
}
try {
ok("detect-url", Clip.detectKind("https://example.com/x").kind === "url");
ok("detect-color", Clip.detectKind("#ff0000").kind === "color");
ok("detect-image", Clip.detectKind("/tmp/shot.png").kind === "image");
ok("detect-path", Clip.detectKind("/etc/hosts").kind === "path");
ok("detect-text", Clip.detectKind("just some words").kind === "text");
const sorted = Clip.sortRows([
{
"id": "a",
"title": "a",
"body": "a",
"pinned": false,
"createdAt": 100
},
{
"id": "b",
"title": "b",
"body": "b",
"pinned": false,
"createdAt": 200
},
{
"id": "c",
"title": "c",
"body": "c",
"pinned": true,
"createdAt": 50
}
]);
ok("sort-pinned-first", sorted[0].id === "c");
ok("sort-recency-desc", sorted[1].id === "b" && sorted[2].id === "a");
const filtered = Clip.filterRows([
{
"id": "x",
"title": "hello",
"body": "world",
"pinned": false,
"createdAt": 1
},
{
"id": "y",
"title": "foo",
"body": "bar",
"pinned": false,
"createdAt": 2
}
], "foo");
ok("filter-match", filtered.length === 1 && filtered[0].id === "y");
const imageRow = Clip.cloneRow({
"id": "i",
"body": "/tmp/p.png",
"pinned": false,
"createdAt": Date.now()
});
ok("clone-image", imageRow.isImage === true && imageRow.title === "Image" && imageRow.imagePath === "/tmp/p.png");
const colorRow = Clip.cloneRow({
"id": "c2",
"body": "#abc",
"pinned": false,
"createdAt": Date.now()
});
ok("clone-swatch", colorRow.kind === "color" && colorRow.swatch === "#aabbcc");
} catch (error) {
ok("threw:" + error, false);
}
const fail = cases.filter(c => {
return !c.pass;
}).length;
return JSON.stringify({
"pass": cases.length - fail,
"fail": fail,
"cases": cases
});
}
FileView {
id: clipboardHistoryFile
path: root.historyPath
blockWrites: true
atomicWrites: true
printErrors: false
onLoaded: root.loadHistoryText(clipboardHistoryFile.text())
onLoadFailed: {
root.persistenceReady = true;
root.saveHistory();
}
}
Timer {
id: pasteFocusTimer
interval: 25
onTriggered: {
root.focusReply = "";
pasteFocusQuery.running = true;
}
}
Process {
id: pasteFocusQuery
command: ["/usr/bin/niri", "msg", "--json", "focused-window"]
stdout: StdioCollector {
onStreamFinished: root.focusReply = text
}
}
Connections {
target: pasteFocusQuery
function onExited(exitCode) {
root.checkPasteFocus(exitCode);
}
}
Timer {
id: vaultCopyFailsafe
onTriggered: root.resumeAfterVaultCopy()
}
Process {
id: clipboardWatcher
command: ["wl-paste", "--type", "text/plain", "--watch", "sh", "-c", "cat; printf '\\036'"]
running: !root.vaultCopyActive
onRunningChanged: {
if (root.vaultCopyActive && !running && !root.vaultPauseAcknowledged) {
root.vaultPauseAcknowledged = true;
root.vaultCapturePaused();
}
}
stdout: SplitParser {
splitMarker: "\u001e"
onRead: data => {
return root.captureClipboard(data);
}
}
}
}