1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-14 09:42:10 -04:00

clipboard: introduce native clipboard, clip-persist, clip-storage functionality

This commit is contained in:
bbedward
2025-12-11 09:41:07 -05:00
parent 7c88865d67
commit 6d62229b5f
41 changed files with 4372 additions and 547 deletions

View File

@@ -1,9 +1,7 @@
pragma ComponentBehavior: Bound
import QtQuick
import Quickshell
import Quickshell.Hyprland
import Quickshell.Io
import qs.Common
import qs.Modals.Common
import qs.Services
@@ -27,33 +25,27 @@ DankModal {
property Component clipboardContent
property int activeImageLoads: 0
readonly property int maxConcurrentLoads: 3
readonly property bool clipboardAvailable: DMSService.isConnected && DMSService.capabilities.includes("clipboard")
function updateFilteredModel() {
filteredClipboardModel.clear();
for (var i = 0; i < clipboardModel.count; i++) {
const entry = clipboardModel.get(i).entry;
if (searchText.trim().length === 0) {
filteredClipboardModel.append({
"entry": entry
});
} else {
const content = getEntryPreview(entry).toLowerCase();
if (content.includes(searchText.toLowerCase())) {
filteredClipboardModel.append({
"entry": entry
});
}
}
const query = searchText.trim();
if (query.length === 0) {
clipboardEntries = internalEntries;
} else {
const lowerQuery = query.toLowerCase();
clipboardEntries = internalEntries.filter(entry => entry.preview.toLowerCase().includes(lowerQuery));
}
clipboardHistoryModal.totalCount = filteredClipboardModel.count;
if (filteredClipboardModel.count === 0) {
totalCount = clipboardEntries.length;
if (clipboardEntries.length === 0) {
keyboardNavigationActive = false;
selectedIndex = 0;
} else if (selectedIndex >= filteredClipboardModel.count) {
selectedIndex = filteredClipboardModel.count - 1;
} else if (selectedIndex >= clipboardEntries.length) {
selectedIndex = clipboardEntries.length - 1;
}
}
property var internalEntries: []
function toggle() {
if (shouldBeVisible) {
hide();
@@ -63,15 +55,19 @@ DankModal {
}
function show() {
if (!clipboardAvailable) {
ToastService.showError(I18n.tr("Clipboard service not available"));
return;
}
open();
clipboardHistoryModal.searchText = "";
clipboardHistoryModal.activeImageLoads = 0;
clipboardHistoryModal.shouldHaveFocus = true;
searchText = "";
activeImageLoads = 0;
shouldHaveFocus = true;
refreshClipboard();
keyboardController.reset();
Qt.callLater(function () {
if (contentLoader.item && contentLoader.item.searchField) {
if (contentLoader.item?.searchField) {
contentLoader.item.searchField.text = "";
contentLoader.item.searchField.forceActiveFocus();
}
@@ -80,60 +76,90 @@ DankModal {
function hide() {
close();
clipboardHistoryModal.searchText = "";
clipboardHistoryModal.activeImageLoads = 0;
updateFilteredModel();
searchText = "";
activeImageLoads = 0;
internalEntries = [];
clipboardEntries = [];
keyboardController.reset();
cleanupTempFiles();
}
function cleanupTempFiles() {
Quickshell.execDetached(["sh", "-c", "rm -f /tmp/clipboard_*.png"]);
}
function refreshClipboard() {
clipboardProcesses.refresh();
DMSService.sendRequest("clipboard.getHistory", null, function (response) {
if (response.error) {
console.warn("ClipboardHistoryModal: Failed to get history:", response.error);
return;
}
internalEntries = response.result || [];
updateFilteredModel();
});
}
function copyEntry(entry) {
const entryId = entry.split('\t')[0];
Quickshell.execDetached(["sh", "-c", `cliphist decode ${entryId} | wl-copy`]);
ToastService.showInfo(I18n.tr("Copied to clipboard"));
hide();
DMSService.sendRequest("clipboard.getEntry", {
"id": entry.id
}, function (response) {
if (response.error) {
ToastService.showError(I18n.tr("Failed to copy entry"));
return;
}
const fullEntry = response.result;
if (fullEntry.isImage) {
ToastService.showInfo(I18n.tr("Image copied to clipboard"));
} else {
DMSService.sendRequest("clipboard.copy", {
"text": fullEntry.data
}, function (copyResponse) {
if (copyResponse.error) {
ToastService.showError(I18n.tr("Failed to copy"));
return;
}
ToastService.showInfo(I18n.tr("Copied to clipboard"));
});
}
hide();
});
}
function deleteEntry(entry) {
clipboardProcesses.deleteEntry(entry);
DMSService.sendRequest("clipboard.deleteEntry", {
"id": entry.id
}, function (response) {
if (response.error) {
console.warn("ClipboardHistoryModal: Failed to delete entry:", response.error);
return;
}
internalEntries = internalEntries.filter(e => e.id !== entry.id);
updateFilteredModel();
if (clipboardEntries.length === 0) {
keyboardNavigationActive = false;
selectedIndex = 0;
} else if (selectedIndex >= clipboardEntries.length) {
selectedIndex = clipboardEntries.length - 1;
}
});
}
function clearAll() {
clipboardProcesses.clearAll();
DMSService.sendRequest("clipboard.clearHistory", null, function (response) {
if (response.error) {
console.warn("ClipboardHistoryModal: Failed to clear history:", response.error);
return;
}
internalEntries = [];
clipboardEntries = [];
totalCount = 0;
});
}
function getEntryPreview(entry) {
let content = entry.replace(/^\s*\d+\s+/, "");
if (content.includes("image/") || content.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(content)) {
const dimensionMatch = content.match(/(\d+)x(\d+)/);
if (dimensionMatch) {
return `Image ${dimensionMatch[1]}×${dimensionMatch[2]}`;
}
const typeMatch = content.match(/\b(png|jpg|jpeg|gif|bmp|webp)\b/i);
if (typeMatch) {
return `Image (${typeMatch[1].toUpperCase()})`;
}
return "Image";
}
if (content.length > ClipboardConstants.previewLength) {
return content.substring(0, ClipboardConstants.previewLength) + "...";
}
return content;
return entry.preview || "";
}
function getEntryType(entry) {
if (entry.includes("image/") || entry.includes("binary data") || /\.(png|jpg|jpeg|gif|bmp|webp)/i.test(entry) || /\b(png|jpg|jpeg|gif|bmp|webp)\b/i.test(entry)) {
if (entry.isImage) {
return "image";
}
if (entry.length > ClipboardConstants.longTextThreshold) {
if (entry.size > ClipboardConstants.longTextThreshold) {
return "long_text";
}
return "text";
@@ -168,55 +194,18 @@ DankModal {
} else if (clipboardHistoryModal.shouldBeVisible) {
clipboardHistoryModal.shouldHaveFocus = true;
clipboardHistoryModal.modalFocusScope.forceActiveFocus();
if (clipboardHistoryModal.contentLoader.item && clipboardHistoryModal.contentLoader.item.searchField) {
if (clipboardHistoryModal.contentLoader.item?.searchField) {
clipboardHistoryModal.contentLoader.item.searchField.forceActiveFocus();
}
}
}
}
property alias filteredClipboardModel: filteredClipboardModel
property alias clipboardModel: clipboardModel
property var confirmDialog: clearConfirmDialog
ListModel {
id: clipboardModel
}
ListModel {
id: filteredClipboardModel
}
ClipboardProcesses {
id: clipboardProcesses
modal: clipboardHistoryModal
clipboardModel: clipboardModel
filteredClipboardModel: filteredClipboardModel
}
IpcHandler {
function open(): string {
clipboardHistoryModal.show();
return "CLIPBOARD_OPEN_SUCCESS";
}
function close(): string {
clipboardHistoryModal.hide();
return "CLIPBOARD_CLOSE_SUCCESS";
}
function toggle(): string {
clipboardHistoryModal.toggle();
return "CLIPBOARD_TOGGLE_SUCCESS";
}
target: "clipboard"
}
clipboardContent: Component {
ClipboardContent {
modal: clipboardHistoryModal
filteredModel: filteredClipboardModel
clearConfirmDialog: clipboardHistoryModal.confirmDialog
}
}