clipboard history using cliphist
Lint & Format (JS/TS) / lint-format (pull_request) Successful in 10s
Python / lint-format (pull_request) Successful in 15s
Python / test (pull_request) Successful in 29s
Lint & Format (Rust) / lint-format (pull_request) Successful in 1m6s

This commit is contained in:
2026-06-11 14:35:42 +02:00
parent e90f1facb7
commit 130e613eb5
14 changed files with 492 additions and 70 deletions
+150
View File
@@ -0,0 +1,150 @@
pragma Singleton
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Config
import "../scripts/fuzzysort.js" as Fuzzy
Singleton {
id: root
property string cliphistBinary: "cliphist"
property list<string> entries: []
property real pasteDelay: 0.05
readonly property var preparedEntries: entries.map(a => ({
name: Fuzzy.prepare(`${a.replace(/^\s*\S+\s+/, "")}`),
entry: a
}))
property string pressPasteCommand: "ydotool key -d 1 29:1 47:1 47:0 29:0"
property real scoreThreshold: 0.2
function copy(entry) {
if (root.cliphistBinary.includes("cliphist"))
Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy`]);
else {
const entryNumber = entry.split("\t")[0];
Quickshell.execDetached(["bash", "-c", `${root.cliphistBinary} decode ${entryNumber} | wl-copy`]);
}
}
function deleteEntry(entry) {
deleteProc.deleteEntry(entry);
}
function entryIsImage(entry) {
return !!(/^\d+\t\[\[.*binary data.*\d+x\d+.*\]\]$/.test(entry));
}
function fuzzyQuery(search: string): var {
if (search.trim() === "") {
return entries;
}
return Fuzzy.go(search, preparedEntries, {
all: true,
key: "name"
}).map(r => {
return r.obj.entry;
});
}
function paste(entry) {
if (root.cliphistBinary.includes("cliphist"))
Quickshell.execDetached(["bash", "-c", `printf '${shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]);
else {
const entryNumber = entry.split("\t")[0];
Quickshell.execDetached(["bash", "-c", `${root.cliphistBinary} decode ${entryNumber} | wl-copy; ${root.pressPasteCommand}`]);
}
}
function refresh() {
readProc.buffer = [];
readProc.running = true;
}
function shellSingleQuoteEscape(str) {
return String(str).replace(/'/g, "'\\''");
}
function wipe() {
wipeProc.running = true;
}
Process {
id: deleteProc
property string entry: ""
function deleteEntry(entry) {
deleteProc.entry = entry;
deleteProc.running = true;
deleteProc.entry = "";
}
command: ["bash", "-c", `echo '${root.shellSingleQuoteEscape(deleteProc.entry)}' | ${root.cliphistBinary} delete`]
onExited: (exitCode, exitStatus) => {
root.refresh();
}
}
Process {
id: wipeProc
command: [root.cliphistBinary, "wipe"]
onExited: (exitCode, exitStatus) => {
root.refresh();
}
}
Connections {
function onClipboardTextChanged() {
delayedUpdateTimer.restart();
}
target: Quickshell
}
Timer {
id: delayedUpdateTimer
interval: 50
repeat: false
onTriggered: {
root.refresh();
}
}
Process {
id: readProc
property list<string> buffer: []
command: [root.cliphistBinary, "list"]
stdout: SplitParser {
onRead: line => {
readProc.buffer.push(line);
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode === 0) {
root.entries = readProc.buffer;
} else {
console.error("[Cliphist] Failed to refresh with code", exitCode, "and status", exitStatus);
}
}
}
IpcHandler {
function update(): void {
root.refresh();
}
target: "cliphistService"
}
}