noctalia-shell/Modules/Launcher/Plugins/ClipboardPlugin.qml
2025-09-03 08:44:10 -04:00

215 lines
6.2 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import QtQuick
import Quickshell
import qs.Commons
import qs.Services
QtObject {
id: root
// Plugin metadata
property string name: "Clipboard History"
property var launcher: null
// Plugin capabilities
property bool handleSearch: false // Don't handle regular search
// Initialize plugin
function init() {
Logger.log("ClipboardPlugin", "Initialized")
}
// Called when launcher opens
function onOpened() {
// Refresh clipboard history when launcher opens
CliphistService.list(100)
}
// Check if this plugin handles the command
function handleCommand(searchText) {
return searchText.startsWith(">clip")
}
// Return available commands when user types ">"
function commands() {
return [{
"name": ">clip",
"description": "Search clipboard history",
"icon": "content_paste",
"isImage": false,
"onActivate": function () {
launcher.setSearchText(">clip ")
}
}, {
"name": ">clip clear",
"description": "Clear all clipboard history",
"icon": "delete_sweep",
"isImage": false,
"onActivate": function () {
CliphistService.wipeAll()
launcher.close()
}
}]
}
// Get search results
function getResults(searchText) {
if (!searchText.startsWith(">clip")) {
return []
}
const results = []
const query = searchText.slice(5).trim()
// Special command: clear
if (query === "clear") {
return [{
"name": "Clear Clipboard History",
"description": "Remove all items from clipboard history",
"icon": "delete_sweep",
"onActivate": function () {
CliphistService.wipeAll()
launcher.close()
}
}]
}
// Search clipboard items
const searchTerm = query.toLowerCase()
// Force dependency update
const _rev = CliphistService.revision
const items = CliphistService.items || []
// Filter and format results
items.forEach(function (item) {
const preview = (item.preview || "").toLowerCase()
// Skip if search term doesn't match
if (searchTerm && preview.indexOf(searchTerm) === -1) {
return
}
// Format the result based on type
let entry
if (item.isImage) {
entry = formatImageEntry(item)
} else {
entry = formatTextEntry(item)
}
// Add activation handler
entry.onActivate = function () {
CliphistService.copyToClipboard(item.id)
launcher.close()
}
results.push(entry)
})
// Show empty state if no results
if (results.length === 0) {
results.push({
"name": searchTerm ? "No matching clipboard items" : "Clipboard is empty",
"description": searchTerm ? `No items containing "${query}"` : "Copy something to see it here",
"icon": "content_paste_off",
"isImage": false,
"onActivate": function () {// Do nothing
}
})
}
return results
}
// Helper: Format image clipboard entry with actual image data
function formatImageEntry(item) {
const meta = parseImageMeta(item.preview)
// Get the actual image data/path from the clipboard service
// This assumes CliphistService provides either a path or base64 data
let imageData = null
// Try to get image data from the service
// Method 1: If the service provides a file path
if (item.imagePath) {
imageData = "file://" + item.imagePath
} // Method 2: If the service provides base64 data
else if (item.imageData) {
imageData = ClipHistService.getImageData(item.id)
// "data:" + (item.mime || "image/png") + ";base64," + item.imageData
} // Method 3: If we need to fetch it from the service
// else if (item.id) {
// // Some clipboard services might require fetching the image separately
// // This would depend on your CliphistService implementation
// imageData = CliphistService.getImageData ? CliphistService.getImageData(item.id) : null
// }
return {
"name": meta ? `Image ${meta.w}×${meta.h}` : "Image",
"description": meta ? `${meta.fmt} ${meta.size}` : item.mime || "Image data",
"icon": "image",
"isImage": true,
"imageSource": imageData,
"imageWidth": meta ? meta.w : 0,
"imageHeight": meta ? meta.h : 0,
"clipboardId"// Add clipboard item ID for potential async loading
: item.id
}
}
// Helper: Format text clipboard entry with preview
function formatTextEntry(item) {
const preview = (item.preview || "").trim()
const lines = preview.split('\n').filter(l => l.trim())
// Use first line as title, limit length
let title = lines[0] || "Empty text"
if (title.length > 60) {
title = title.substring(0, 57) + "..."
}
// Use second line or character count as description
let description = ""
if (lines.length > 1) {
description = lines[1]
if (description.length > 80) {
description = description.substring(0, 77) + "..."
}
} else {
const chars = preview.length
const words = preview.split(/\s+/).length
description = `${chars} characters, ${words} word${words !== 1 ? 's' : ''}`
}
return {
"name": title,
"description": description,
"icon": "description",
"isImage": false
}
}
// Helper: Parse image metadata from preview string
function parseImageMeta(preview) {
const re = /\[\[\s*binary data\s+([\d\.]+\s*(?:KiB|MiB|GiB|B))\s+(\w+)\s+(\d+)x(\d+)\s*\]\]/i
const match = (preview || "").match(re)
if (!match) {
return null
}
return {
"size": match[1],
"fmt": (match[2] || "").toUpperCase(),
"w": Number(match[3]),
"h": Number(match[4])
}
}
// Public method to get image data for a clipboard item
// This can be called by the launcher when rendering
function getImageForItem(clipboardId) {
return CliphistService.getImageData ? CliphistService.getImageData(clipboardId) : null
}
}