mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -05:00
meta: support async launcher plugins, cached GIFs, paste on launcher v2
action - Preparations for DankGifSearch plugin
This commit is contained in:
@@ -29,16 +29,7 @@ DankModal {
|
||||
property int activeImageLoads: 0
|
||||
readonly property int maxConcurrentLoads: 3
|
||||
readonly property bool clipboardAvailable: DMSService.isConnected && (DMSService.capabilities.length === 0 || DMSService.capabilities.includes("clipboard"))
|
||||
property bool wtypeAvailable: false
|
||||
|
||||
Process {
|
||||
id: wtypeCheck
|
||||
command: ["which", "wtype"]
|
||||
running: true
|
||||
onExited: exitCode => {
|
||||
clipboardHistoryModal.wtypeAvailable = (exitCode === 0);
|
||||
}
|
||||
}
|
||||
readonly property bool wtypeAvailable: SessionService.wtypeAvailable
|
||||
|
||||
Process {
|
||||
id: wtypeProcess
|
||||
|
||||
@@ -2,6 +2,7 @@ pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import "Scorer.js" as Scorer
|
||||
@@ -41,6 +42,47 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: PluginService
|
||||
function onRequestLauncherUpdate(pluginId) {
|
||||
if (activePluginId === pluginId || searchQuery) {
|
||||
performSearch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: wtypeProcess
|
||||
command: ["wtype", "-M", "ctrl", "-P", "v", "-p", "v", "-m", "ctrl"]
|
||||
running: false
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: pasteTimer
|
||||
interval: 200
|
||||
repeat: false
|
||||
onTriggered: wtypeProcess.running = true
|
||||
}
|
||||
|
||||
function pasteSelected() {
|
||||
if (!selectedItem)
|
||||
return;
|
||||
if (!SessionService.wtypeAvailable) {
|
||||
ToastService.showError("wtype not available - install wtype for paste support");
|
||||
return;
|
||||
}
|
||||
|
||||
const pluginId = selectedItem.pluginId;
|
||||
if (!pluginId)
|
||||
return;
|
||||
const pasteText = AppSearchService.getPluginPasteText(pluginId, selectedItem.data);
|
||||
if (!pasteText)
|
||||
return;
|
||||
Quickshell.execDetached(["dms", "cl", "copy", pasteText]);
|
||||
itemExecuted();
|
||||
pasteTimer.start();
|
||||
}
|
||||
|
||||
readonly property var sectionDefinitions: [
|
||||
{
|
||||
id: "calculator",
|
||||
|
||||
@@ -209,6 +209,10 @@ FocusScope {
|
||||
return;
|
||||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
controller.pasteSelected();
|
||||
return;
|
||||
}
|
||||
if (actionPanel.expanded && actionPanel.selectedActionIndex > 0) {
|
||||
actionPanel.executeSelectedAction();
|
||||
} else {
|
||||
|
||||
@@ -109,6 +109,18 @@ Rectangle {
|
||||
color: Theme.primaryText
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.margins: Theme.spacingXS
|
||||
width: 40
|
||||
height: 16
|
||||
fillMode: Image.PreserveAspectFit
|
||||
source: root.item?.data?.attribution || ""
|
||||
visible: source !== ""
|
||||
opacity: 0.9
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -855,6 +855,21 @@ Singleton {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getPluginPasteText(pluginId, item) {
|
||||
if (typeof PluginService === "undefined")
|
||||
return null;
|
||||
|
||||
const instance = PluginService.pluginInstances[pluginId];
|
||||
if (!instance)
|
||||
return null;
|
||||
|
||||
if (typeof instance.getPasteText === "function") {
|
||||
return instance.getPasteText(item);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function searchPluginItems(query) {
|
||||
if (typeof PluginService === "undefined")
|
||||
return [];
|
||||
|
||||
@@ -592,6 +592,13 @@ Singleton {
|
||||
return SettingsData.getPluginSetting(pluginId, key, defaultValue);
|
||||
}
|
||||
|
||||
function getPluginPath(pluginId) {
|
||||
const plugin = availablePlugins[pluginId];
|
||||
if (!plugin)
|
||||
return "";
|
||||
return plugin.pluginDirectory || "";
|
||||
}
|
||||
|
||||
function saveAllPluginSettings() {
|
||||
SettingsData.savePluginSettings();
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ Singleton {
|
||||
}
|
||||
|
||||
property bool loginctlAvailable: false
|
||||
property bool wtypeAvailable: false
|
||||
property string sessionId: ""
|
||||
property string sessionPath: ""
|
||||
property bool locked: false
|
||||
@@ -59,6 +60,7 @@ Singleton {
|
||||
detectElogindProcess.running = true;
|
||||
detectHibernateProcess.running = true;
|
||||
detectPrimeRunProcess.running = true;
|
||||
detectWtypeProcess.running = true;
|
||||
console.info("SessionService: Native inhibitor available:", nativeInhibitorAvailable);
|
||||
if (!SettingsData.loginctlLockIntegration) {
|
||||
console.log("SessionService: loginctl lock integration disabled by user");
|
||||
@@ -124,6 +126,15 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: detectWtypeProcess
|
||||
running: false
|
||||
command: ["which", "wtype"]
|
||||
onExited: exitCode => {
|
||||
wtypeAvailable = (exitCode === 0);
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: detectPrimeRunProcess
|
||||
running: false
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
|
||||
Image {
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string imagePath: ""
|
||||
property int maxCacheSize: 512
|
||||
property int status: isAnimated ? animatedImg.status : staticImg.status
|
||||
property int fillMode: Image.PreserveAspectCrop
|
||||
|
||||
readonly property bool isRemoteUrl: imagePath.startsWith("http://") || imagePath.startsWith("https://")
|
||||
readonly property bool isAnimated: {
|
||||
if (!imagePath)
|
||||
return false;
|
||||
const lower = imagePath.toLowerCase();
|
||||
return lower.endsWith(".gif") || lower.endsWith(".webp");
|
||||
}
|
||||
readonly property string normalizedPath: {
|
||||
if (!imagePath)
|
||||
return "";
|
||||
@@ -30,7 +38,7 @@ Image {
|
||||
}
|
||||
|
||||
readonly property string imageHash: normalizedPath ? djb2Hash(normalizedPath) : ""
|
||||
readonly property string cachePath: imageHash && !isRemoteUrl ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : ""
|
||||
readonly property string cachePath: imageHash && !isRemoteUrl && !isAnimated ? `${Paths.stringify(Paths.imagecache)}/${imageHash}@${maxCacheSize}x${maxCacheSize}.png` : ""
|
||||
readonly property string encodedImagePath: {
|
||||
if (!normalizedPath)
|
||||
return "";
|
||||
@@ -39,39 +47,56 @@ Image {
|
||||
return "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
}
|
||||
|
||||
asynchronous: true
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
sourceSize.width: maxCacheSize
|
||||
sourceSize.height: maxCacheSize
|
||||
smooth: true
|
||||
AnimatedImage {
|
||||
id: animatedImg
|
||||
anchors.fill: parent
|
||||
visible: root.isAnimated
|
||||
asynchronous: true
|
||||
fillMode: root.fillMode
|
||||
source: root.isAnimated ? root.imagePath : ""
|
||||
playing: visible && status === AnimatedImage.Ready
|
||||
}
|
||||
|
||||
Image {
|
||||
id: staticImg
|
||||
anchors.fill: parent
|
||||
visible: !root.isAnimated
|
||||
asynchronous: true
|
||||
fillMode: root.fillMode
|
||||
sourceSize.width: root.maxCacheSize
|
||||
sourceSize.height: root.maxCacheSize
|
||||
smooth: true
|
||||
|
||||
onStatusChanged: {
|
||||
if (source == root.cachePath && status === Image.Error) {
|
||||
source = root.encodedImagePath;
|
||||
return;
|
||||
}
|
||||
if (root.isRemoteUrl || source != root.encodedImagePath || status !== Image.Ready || !root.cachePath)
|
||||
return;
|
||||
Paths.mkdir(Paths.imagecache);
|
||||
const grabPath = root.cachePath;
|
||||
if (visible && width > 0 && height > 0 && Window.window?.visible) {
|
||||
grabToImage(res => res.saveToFile(grabPath));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onImagePathChanged: {
|
||||
if (!imagePath) {
|
||||
source = "";
|
||||
staticImg.source = "";
|
||||
return;
|
||||
}
|
||||
if (isAnimated)
|
||||
return;
|
||||
if (isRemoteUrl) {
|
||||
source = imagePath;
|
||||
staticImg.source = imagePath;
|
||||
return;
|
||||
}
|
||||
Paths.mkdir(Paths.imagecache);
|
||||
const hash = djb2Hash(normalizedPath);
|
||||
const cPath = hash ? `${Paths.stringify(Paths.imagecache)}/${hash}@${maxCacheSize}x${maxCacheSize}.png` : "";
|
||||
const encoded = "file://" + normalizedPath.split('/').map(s => encodeURIComponent(s)).join('/');
|
||||
source = cPath || encoded;
|
||||
}
|
||||
|
||||
onStatusChanged: {
|
||||
if (source == cachePath && status === Image.Error) {
|
||||
source = encodedImagePath;
|
||||
return;
|
||||
}
|
||||
if (isRemoteUrl || source != encodedImagePath || status !== Image.Ready || !cachePath)
|
||||
return;
|
||||
Paths.mkdir(Paths.imagecache);
|
||||
const grabPath = cachePath;
|
||||
if (visible && width > 0 && height > 0 && Window.window?.visible) {
|
||||
grabToImage(res => res.saveToFile(grabPath));
|
||||
}
|
||||
staticImg.source = cPath || encoded;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user