1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-06-08 04:09:15 -04:00

feat(Clipboard): Implement clipboard/settings search functionality in DMS Launcher

This commit is contained in:
purian23
2026-05-16 17:43:52 -04:00
parent 9f2ae6241e
commit c923c43322
11 changed files with 426 additions and 17 deletions
@@ -76,6 +76,13 @@ Rectangle {
}
}
break;
case "clipboard":
if (selectedItem?.actions) {
for (var i = 0; i < selectedItem.actions.length; i++) {
result.push(selectedItem.actions[i]);
}
}
break;
}
return result;
}
@@ -0,0 +1,67 @@
pragma ComponentBehavior: Bound
import QtQuick
import qs.Common
import qs.Services
import qs.Widgets
Rectangle {
id: root
property var entry: null
property string cachedImageData: ""
property var _requestedEntryId: null
readonly property bool canLoadImage: !!entry?.isImage && (entry?.mimeType ?? "").startsWith("image/")
readonly property string sourceUrl: cachedImageData.length > 0 ? "data:" + (entry?.mimeType ?? "image/png") + ";base64," + cachedImageData : ""
radius: Math.max(6, Theme.cornerRadius - 2)
clip: true
color: Theme.surfaceContainerHigh
border.color: Theme.withAlpha(Theme.outline, 0.16)
border.width: 1
onEntryChanged: reloadPreview()
Component.onCompleted: reloadPreview()
function reloadPreview() {
cachedImageData = "";
if (!canLoadImage || !entry?.id) {
_requestedEntryId = null;
return;
}
const entryId = entry.id;
_requestedEntryId = entryId;
DMSService.sendRequest("clipboard.getEntry", {
"id": entryId
}, function (response) {
if (_requestedEntryId !== entryId)
return;
if (response.error)
return;
const data = response.result?.data ?? "";
if (data.length > 0)
cachedImageData = data;
});
}
Image {
id: previewImage
anchors.fill: parent
source: root.sourceUrl
asynchronous: true
cache: false
smooth: true
fillMode: Image.PreserveAspectCrop
visible: status === Image.Ready
}
DankIcon {
anchors.centerIn: parent
name: "image"
size: Math.min(22, Math.max(16, root.height * 0.46))
color: Theme.primary
visible: previewImage.status !== Image.Ready
}
}
+144 -7
View File
@@ -44,7 +44,10 @@ Item {
signal searchQueryRequested(string query)
onActiveChanged: {
if (!active) {
if (active) {
if (clipboardSearchEnabledInAll())
ClipboardService.ensureLauncherHistory();
} else {
SessionData.addLauncherHistory(searchQuery);
sections = [];
@@ -69,6 +72,28 @@ Item {
AppSearchService.invalidateLauncherCache();
_clearModeCache();
}
function onLauncherPluginVisibilityChanged() {
AppSearchService.invalidateLauncherCache();
_clearModeCache();
if (active)
performSearch();
}
function onBuiltInPluginSettingsChanged() {
AppSearchService.invalidateLauncherCache();
_clearModeCache();
if (active)
performSearch();
}
}
Connections {
target: ClipboardService
function onInternalEntriesChanged() {
if (!active || !clipboardSearchEnabledInAll())
return;
if (searchMode === "all" && searchQuery.length >= 2)
performSearch();
}
}
Connections {
@@ -124,6 +149,18 @@ Item {
function pasteSelected() {
if (!selectedItem)
return;
if (selectedItem.type === "clipboard") {
if (SettingsData.clipboardEnterToPaste) {
ClipboardService.copyEntry(selectedItem.data, function () {
root.itemExecuted();
});
} else {
ClipboardService.pasteEntry(selectedItem.data, function () {
root.itemExecuted();
});
}
return;
}
if (!SessionService.wtypeAvailable) {
ToastService.showError(I18n.tr("wtype not available - install wtype for paste support"));
return;
@@ -155,6 +192,20 @@ Item {
priority: 2,
defaultViewMode: "list"
},
{
id: "settings",
title: I18n.tr("Settings", "settings window title"),
icon: "settings",
priority: 2.35,
defaultViewMode: "list"
},
{
id: "clipboard",
title: I18n.tr("Clipboard"),
icon: "content_paste",
priority: 2.45,
defaultViewMode: "list"
},
{
id: "browse_plugins",
title: I18n.tr("Browse"),
@@ -352,6 +403,9 @@ Item {
searchQuery = query;
searchDebounce.restart();
if (searchMode === "all" && clipboardSearchEnabledInAll() && query.length >= 2)
ClipboardService.ensureLauncherHistory();
var filesInAll = searchMode === "all" && (SettingsData.dankLauncherV2IncludeFilesInAll || SettingsData.dankLauncherV2IncludeFoldersInAll);
if (searchMode !== "plugins" && (searchMode === "files" || query.startsWith("/") || filesInAll) && query.length > 0) {
fileSearchDebounce.restart();
@@ -370,6 +424,8 @@ Item {
searchMode = mode;
modeChanged(mode);
performSearch();
if (mode === "all" && clipboardSearchEnabledInAll() && searchQuery.length >= 2)
ClipboardService.ensureLauncherHistory();
var filesInAll = mode === "all" && (SettingsData.dankLauncherV2IncludeFilesInAll || SettingsData.dankLauncherV2IncludeFoldersInAll) && searchQuery.length > 0;
if (mode === "files" || filesInAll) {
fileSearchDebounce.restart();
@@ -612,7 +668,7 @@ Item {
if (triggerMatch.isBuiltIn) {
var builtInItems = AppSearchService.getBuiltInLauncherItems(triggerMatch.pluginId, triggerMatch.query);
for (var j = 0; j < builtInItems.length; j++) {
allItems.push(transformBuiltInLauncherItem(builtInItems[j], triggerMatch.pluginId));
allItems.push(transformBuiltInSearchItem(builtInItems[j], triggerMatch.pluginId));
}
}
@@ -748,7 +804,7 @@ Item {
var builtInItems = AppSearchService.getBuiltInLauncherItems(pluginFilter, searchQuery);
for (var j = 0; j < builtInItems.length; j++) {
allItems.push(transformBuiltInLauncherItem(builtInItems[j], pluginFilter));
allItems.push(transformBuiltInSearchItem(builtInItems[j], pluginFilter));
}
} else {
var emptyTriggerPlugins = getEmptyTriggerPlugins();
@@ -764,7 +820,7 @@ Item {
var pluginId = builtInLauncherPlugins[i];
var blItems = AppSearchService.getBuiltInLauncherItems(pluginId, searchQuery);
for (var j = 0; j < blItems.length; j++) {
allItems.push(transformBuiltInLauncherItem(blItems[j], pluginId));
allItems.push(transformBuiltInSearchItem(blItems[j], pluginId));
}
}
}
@@ -799,6 +855,7 @@ Item {
}
if (searchMode === "all") {
appendSharedAllResults(allItems, searchQuery);
if (searchQuery && searchQuery.length >= 2) {
_pluginPhasePending = true;
_phase1Items = allItems.slice();
@@ -814,7 +871,7 @@ Item {
if (plugin.isBuiltIn) {
var blItems = AppSearchService.getBuiltInLauncherItems(plugin.id, searchQuery);
for (var j = 0; j < blItems.length; j++)
allItems.push(transformBuiltInLauncherItem(blItems[j], plugin.id));
allItems.push(transformBuiltInSearchItem(blItems[j], plugin.id));
} else {
var pItems = getPluginItems(plugin.id, searchQuery);
for (var j = 0; j < pItems.length; j++)
@@ -883,11 +940,13 @@ Item {
if (currentVersion !== _searchVersion)
return;
var plugin = allPluginsOrdered[i];
if (plugin.isBuiltIn && (plugin.id === "dms_settings_search" || plugin.id === "dms_clipboard_search"))
continue;
if (plugin.isBuiltIn) {
var blItems = AppSearchService.getBuiltInLauncherItems(plugin.id, searchQuery);
var blLimit = Math.min(blItems.length, maxPerPlugin);
for (var j = 0; j < blLimit; j++) {
var item = transformBuiltInLauncherItem(blItems[j], plugin.id);
var item = transformBuiltInSearchItem(blItems[j], plugin.id);
item._preScored = 900 - j;
allItems.push(item);
}
@@ -1110,10 +1169,56 @@ Item {
return Transform.transformBuiltInLauncherItem(item, pluginId, I18n.tr("Open"));
}
function transformBuiltInSearchItem(item, pluginId) {
if (pluginId === "dms_clipboard_search" || item.type === "clipboard")
return transformClipboardEntry(item.data || item);
return transformBuiltInLauncherItem(item, pluginId);
}
function transformFileResult(file) {
return Transform.transformFileResult(file, I18n.tr("Open"), I18n.tr("Open folder"), I18n.tr("Copy path"), I18n.tr("Open in terminal"));
}
function transformClipboardEntry(entry) {
var copyLabel = I18n.tr("Copy");
var pasteLabel = I18n.tr("Paste");
var primaryLabel = SettingsData.clipboardEnterToPaste ? pasteLabel : copyLabel;
var pasteHintLabel = SettingsData.clipboardEnterToPaste ? I18n.tr("Shift+Enter to copy") : I18n.tr("Shift+Enter to paste");
return Transform.transformClipboardItem(entry, copyLabel, pasteLabel, primaryLabel, I18n.tr("Image"), I18n.tr("Text"), I18n.tr("Pinned"), pasteHintLabel, "", I18n.tr("Clipboard"));
}
function builtInLauncherVisibleInAll(pluginId) {
return SettingsData.getBuiltInPluginSetting(pluginId, "enabled", true) && SettingsData.getPluginAllowWithoutTrigger(pluginId);
}
function clipboardSearchEnabledInAll() {
return builtInLauncherVisibleInAll("dms_clipboard_search") && ClipboardService.clipboardAvailable;
}
function appendSharedAllResults(allItems, query) {
if (!query || query.length < 2)
return;
if (builtInLauncherVisibleInAll("dms_settings_search")) {
var settingsItems = AppSearchService.getBuiltInLauncherItems("dms_settings_search", query);
var settingsLimit = Math.min(settingsItems.length, 8);
for (var i = 0; i < settingsLimit; i++) {
settingsItems[i]._preScored = 890 - i;
allItems.push(transformBuiltInSearchItem(settingsItems[i], "dms_settings_search"));
}
}
if (clipboardSearchEnabledInAll()) {
ClipboardService.ensureLauncherHistory();
var clipboardItems = AppSearchService.getBuiltInLauncherItems("dms_clipboard_search", query);
var clipboardLimit = Math.min(clipboardItems.length, 8);
for (var j = 0; j < clipboardLimit; j++) {
clipboardItems[j]._preScored = 840 - j;
allItems.push(transformBuiltInSearchItem(clipboardItems[j], "dms_clipboard_search"));
}
}
}
function detectTrigger(query) {
if (!query || query.length === 0)
return {
@@ -1308,7 +1413,9 @@ Item {
}
function buildDynamicSectionDefs(items) {
var baseDefs = sectionDefinitions.slice();
var baseDefs = sectionDefinitions.map(function (def) {
return Object.assign({}, def);
});
var pluginSections = {};
var order = SettingsData.launcherPluginOrder || [];
var orderMap = {};
@@ -1316,6 +1423,12 @@ Item {
orderMap[order[k]] = k;
var unorderedPriority = 2.6 + order.length * 0.01;
for (var d = 0; d < baseDefs.length; d++) {
var virtualId = baseDefs[d].id === "settings" ? "dms_settings_search" : baseDefs[d].id === "clipboard" ? "dms_clipboard_search" : "";
if (virtualId && orderMap[virtualId] !== undefined)
baseDefs[d].priority = 2.6 + orderMap[virtualId] * 0.01;
}
for (var i = 0; i < items.length; i++) {
var section = items[i].section;
if (!section || !section.startsWith("plugin_"))
@@ -1768,6 +1881,20 @@ Item {
AppSearchService.executePluginItem(item.data, item.pluginId);
}
break;
case "setting":
AppSearchService.executeBuiltInLauncherItem(item.data);
break;
case "clipboard":
if (SettingsData.clipboardEnterToPaste) {
ClipboardService.pasteEntry(item.data, function () {
root.itemExecuted();
});
} else {
ClipboardService.copyEntry(item.data, function () {
root.itemExecuted();
});
}
return;
case "file":
openFile(item.data?.path);
break;
@@ -1803,6 +1930,16 @@ Item {
case "execute":
executeItem(item);
break;
case "clipboard_copy":
ClipboardService.copyEntry(item.data, function () {
root.itemExecuted();
});
return;
case "clipboard_paste":
ClipboardService.pasteEntry(item.data, function () {
root.itemExecuted();
});
return;
case "launch_dgpu":
if (item.type === "app" && item.data) {
launchAppWithNvidia(item.data);
@@ -94,17 +94,19 @@ function transformBuiltInLauncherItem(item, pluginId, openLabel) {
return {
id: item.action || "",
type: "plugin",
type: item.type || "plugin",
name: item.name || "",
subtitle: item.comment || "",
icon: icon,
iconType: iconType,
section: "plugin_" + pluginId,
data: item,
section: item.section || ("plugin_" + pluginId),
data: item.data || item,
pluginId: pluginId,
isBuiltInLauncher: true,
keywords: item.keywords || [],
actions: [],
source: item.source || "",
badgeLabel: item.badgeLabel || "",
primaryAction: {
name: openLabel,
icon: "open_in_new",
@@ -117,6 +119,58 @@ function transformBuiltInLauncherItem(item, pluginId, openLabel) {
};
}
function transformClipboardItem(entry, copyLabel, pasteLabel, primaryLabel, imageLabel, textLabel, pinnedLabel, pasteHintLabel, copyHintLabel, clipboardLabel) {
var preview = (entry.preview || "").toString().replace(/\s+/g, " ").trim();
var isImage = entry.isImage || false;
var title = preview.length > 0 ? preview : imageLabel;
if (title.length > 140)
title = title.substring(0, 137) + "...";
var typeLabel = isImage ? imageLabel : textLabel;
var subtitle = typeLabel;
if (entry.pinned)
subtitle = pinnedLabel + " - " + subtitle;
if (pasteHintLabel)
subtitle += " - " + pasteHintLabel;
if (copyHintLabel)
subtitle += " - " + copyHintLabel;
return {
id: "clipboard_" + (entry.id || entry.hash || title),
type: "clipboard",
name: title,
subtitle: subtitle,
icon: isImage ? "image" : "content_paste",
iconType: "material",
section: "clipboard",
data: entry,
keywords: [preview, isImage ? "image" : "text", "clipboard"],
actions: [
{
name: copyLabel,
icon: "content_copy",
action: "clipboard_copy"
},
{
name: pasteLabel,
icon: "content_paste",
action: "clipboard_paste"
}
],
source: clipboardLabel,
badgeLabel: clipboardLabel,
primaryAction: {
name: primaryLabel,
icon: primaryLabel === pasteLabel ? "content_paste" : "content_copy",
action: primaryLabel === pasteLabel ? "clipboard_paste" : "clipboard_copy"
},
_hName: "",
_hSub: "",
_hRich: false,
_preScored: entry._preScored
};
}
function transformFileResult(file, openLabel, openFolderLabel, copyPathLabel, openTerminalLabel) {
var filename = file.path ? file.path.split("/").pop() : "";
var dirname = file.path ? file.path.substring(0, file.path.lastIndexOf("/")) : "";
@@ -32,6 +32,8 @@ Popup {
var actions = instance.getContextMenuActions(spotlightItem.data);
return Array.isArray(actions) && actions.length > 0;
}
if (spotlightItem.actions && spotlightItem.actions.length > 0)
return true;
return false;
}
@@ -80,6 +82,13 @@ Popup {
hide();
}
function executeLauncherAction(actionData) {
if (!controller || !item || !actionData)
return;
controller.executeAction(item, actionData);
hide();
}
readonly property var menuItems: {
var items = [];
@@ -97,6 +106,19 @@ Popup {
return items;
}
if (item?.type !== "app" && item?.actions && item.actions.length > 0) {
for (var i = 0; i < item.actions.length; i++) {
var genericAct = item.actions[i];
items.push({
type: "item",
icon: genericAct.icon || "play_arrow",
text: genericAct.name || "",
launcherActionData: genericAct
});
}
return items;
}
if (item?.type === "app") {
items.push({
type: "item",
@@ -293,6 +315,8 @@ Popup {
menuItem.action();
else if (menuItem.pluginAction)
executePluginAction(menuItem.pluginAction);
else if (menuItem.launcherActionData)
executeLauncherAction(menuItem.launcherActionData);
else if (menuItem.actionData)
executeDesktopAction(menuItem.actionData);
return;
@@ -500,6 +524,8 @@ Popup {
menuItem.action();
else if (menuItem.pluginAction)
root.executePluginAction(menuItem.pluginAction);
else if (menuItem.launcherActionData)
root.executeLauncherAction(menuItem.launcherActionData);
else if (menuItem.actionData)
root.executeDesktopAction(menuItem.actionData);
}
@@ -33,6 +33,7 @@ Rectangle {
return item.icon || "";
}
}
readonly property bool hasClipboardPreview: item?.type === "clipboard" && item?.data?.isImage === true && (item?.data?.mimeType ?? "").startsWith("image/")
width: parent?.width ?? 200
height: 52
@@ -154,6 +155,14 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter
spacing: Theme.spacingS
ClipboardLauncherPreview {
width: root.hasClipboardPreview ? 56 : 0
height: 36
visible: root.hasClipboardPreview
anchors.verticalCenter: parent.verticalCenter
entry: root.item?.data ?? null
}
Rectangle {
id: allModeToggle
visible: root.item?.type === "plugin_browse"
@@ -208,9 +217,15 @@ Rectangle {
text: {
if (!root.item)
return "";
if ((root.item.badgeLabel ?? "").length > 0)
return root.item.badgeLabel;
switch (root.item.type) {
case "plugin":
return I18n.tr("Plugin");
case "setting":
return I18n.tr("Setting");
case "clipboard":
return I18n.tr("Clipboard");
case "file":
return root.item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
default:
@@ -10,6 +10,8 @@ const Weights = {
typeBonus: {
app: 1000,
plugin: 900,
setting: 850,
clipboard: 825,
file: 800,
action: 600
}
@@ -48,17 +48,24 @@ Rectangle {
return "file://" + raw;
return raw;
}
readonly property bool hasMediaPreview: previewSource.length > 0
readonly property bool hasClipboardPreview: item?.type === "clipboard" && item?.data?.isImage === true && (item?.data?.mimeType ?? "").startsWith("image/")
readonly property bool hasMediaPreview: previewSource.length > 0 || hasClipboardPreview
readonly property bool previewAnimated: previewSource.toLowerCase().indexOf(".gif") >= 0
readonly property string typeLabel: {
if (!item)
return "";
if ((item.badgeLabel ?? "").length > 0)
return item.badgeLabel;
switch (item.type) {
case "plugin_browse":
return I18n.tr("Browse");
case "plugin":
return I18n.tr("Plugin");
case "setting":
return I18n.tr("Setting");
case "clipboard":
return I18n.tr("Clipboard");
case "file":
return item.data?.is_dir ? I18n.tr("Folder") : I18n.tr("File");
default:
@@ -278,7 +285,7 @@ Rectangle {
source: root.previewSource
asynchronous: true
fillMode: Image.PreserveAspectCrop
visible: !root.previewAnimated
visible: !root.hasClipboardPreview && !root.previewAnimated
}
AnimatedImage {
@@ -286,7 +293,13 @@ Rectangle {
source: root.previewSource
fillMode: Image.PreserveAspectCrop
playing: visible
visible: root.previewAnimated
visible: !root.hasClipboardPreview && root.previewAnimated
}
ClipboardLauncherPreview {
anchors.fill: parent
entry: root.item?.data ?? null
visible: root.hasClipboardPreview
}
}
+1 -1
View File
@@ -565,7 +565,7 @@ Item {
spacing: Theme.spacingS
Repeater {
model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search"]
model: ["dms_settings", "dms_notepad", "dms_sysmon", "dms_settings_search", "dms_clipboard_search"]
delegate: Rectangle {
id: pluginDelegate
+28 -3
View File
@@ -211,11 +211,21 @@ Singleton {
},
"dms_settings_search": {
id: "dms_settings_search",
name: I18n.tr("Settings", "settings window title"),
name: I18n.tr("Settings Search"),
cornerIcon: "search",
comment: "DMS",
comment: I18n.tr("DMS Settings"),
defaultTrigger: "?",
isLauncher: true
},
"dms_clipboard_search": {
id: "dms_clipboard_search",
name: I18n.tr("Clipboard"),
cornerIcon: "content_paste",
comment: "DMS",
defaultTrigger: "cb",
isLauncher: true,
viewMode: "list",
viewModeEnforced: true
}
})
@@ -285,6 +295,16 @@ Singleton {
}
function getBuiltInLauncherItems(pluginId, query) {
if (pluginId === "dms_clipboard_search") {
ClipboardService.ensureLauncherHistory();
const trimmed = (query || "").toString().trim();
const entries = trimmed.length === 0 ? ClipboardService.getRecentLauncherEntries(20) : ClipboardService.getLauncherEntries(trimmed, 20, 1);
return entries.map(entry => ({
type: "clipboard",
data: entry
}));
}
if (pluginId !== "dms_settings_search")
return [];
@@ -295,10 +315,15 @@ Singleton {
const r = results[i];
items.push({
name: r.label,
type: "setting",
section: "settings",
icon: "material:" + r.icon,
comment: r.category,
comment: r.description || r.category,
action: "settings_nav:" + r.tabIndex + ":" + r.section,
categories: ["Settings"],
keywords: r.keywords || [],
source: I18n.tr("Settings", "settings window title"),
badgeLabel: I18n.tr("Setting"),
isCore: true,
isBuiltInLauncher: true,
builtInPluginId: pluginId
+63
View File
@@ -26,6 +26,7 @@ Singleton {
property int selectedIndex: 0
property bool keyboardNavigationActive: false
property int refCount: 0
property real _launcherLastRefresh: 0
signal historyCopied
signal historyCleared
@@ -90,6 +91,68 @@ Singleton {
});
}
function ensureLauncherHistory() {
if (!clipboardAvailable) {
return;
}
const now = Date.now();
if (internalEntries.length === 0 || now - _launcherLastRefresh > 5000) {
_launcherLastRefresh = now;
refresh();
}
}
function getLauncherEntries(query, limit, minLength) {
if (!clipboardAvailable) {
return [];
}
const trimmed = (query || "").toString().trim();
const requiredLength = minLength !== undefined ? minLength : 2;
if (trimmed.length < requiredLength) {
return [];
}
const lowerQuery = trimmed.toLowerCase();
const maxItems = limit > 0 ? limit : 8;
const matches = [];
for (var i = 0; i < internalEntries.length; i++) {
const entry = internalEntries[i];
const preview = getEntryPreview(entry).toString();
const typeText = entry.isImage ? "image picture screenshot clipboard" : "text clipboard";
const haystack = (preview + " " + typeText).toLowerCase();
if (haystack.indexOf(lowerQuery) === -1) {
continue;
}
matches.push(entry);
}
matches.sort((a, b) => {
if (a.pinned !== b.pinned)
return b.pinned ? 1 : -1;
return (b.id || 0) - (a.id || 0);
});
return matches.slice(0, maxItems);
}
function getRecentLauncherEntries(limit) {
if (!clipboardAvailable) {
return [];
}
const maxItems = limit > 0 ? limit : 20;
const entries = internalEntries.slice();
entries.sort((a, b) => {
if (a.pinned !== b.pinned)
return b.pinned ? 1 : -1;
return (b.id || 0) - (a.id || 0);
});
return entries.slice(0, maxItems);
}
function reset() {
searchText = "";
selectedIndex = 0;