1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-15 00:32:47 -04:00

app picker: extend App Picker to integrate with mime overrides

- Adds "DMS Opener" as an option (dms-open.desktop)
- Add mime type GO utils
- Add rememberance to App Picker modal
This commit is contained in:
bbedward
2026-05-14 13:06:22 -04:00
parent be4ea71756
commit 018795125e
15 changed files with 1467 additions and 427 deletions
+43 -195
View File
@@ -1,35 +1,16 @@
pragma Singleton
pragma ComponentBehavior: Bound
import Quickshell.Io
import QtQuick
import Quickshell
import qs.Common
import qs.Services
Singleton {
id: root
readonly property var log: Log.scoped("DesktopService")
property var _cache: ({})
property bool gioAvailable: false;
// For the queue that setDefaultApp uses
property var _setDefaultAppQueue: []
property bool _isProcessingQueue: false
Component.onCompleted: {
checkGioAndXdgMime.running = true;
}
Process {
id: checkGioAndXdgMime
command: ["sh", "-c", "which gio && which xdg-mime"]
running: false
onExited: (exitCode) => {
if (exitCode === 0) {
root.gioAvailable = true;
} else {
root.gioAvailable = false;
}
}
}
function resolveIconPath(moddedAppId) {
if (!moddedAppId)
@@ -39,18 +20,15 @@ Singleton {
return _cache[moddedAppId];
const result = (function () {
// 1. Try heuristic lookup (standard)
const entry = DesktopEntries.heuristicLookup(moddedAppId);
let icon = Quickshell.iconPath(entry?.icon, true);
if (icon && icon !== "")
return icon;
// 2. Try the appId itself as an icon name
icon = Quickshell.iconPath(moddedAppId, true);
if (icon && icon !== "")
return icon;
// 3. Try variations of the appId (lowercase, last part)
const appIds = [moddedAppId.toLowerCase()];
const lastPart = moddedAppId.split('.').pop();
if (lastPart && lastPart !== moddedAppId) {
@@ -64,8 +42,6 @@ Singleton {
return icon;
}
// 4. Deep search in all desktop entries (if the above fail)
// This is slow-ish but only happens once for failed icons
const strippedId = moddedAppId.replace(/-bin$/, "").toLowerCase();
const allEntries = DesktopEntries.applications.values;
for (let i = 0; i < allEntries.length; i++) {
@@ -81,7 +57,6 @@ Singleton {
}
}
// 5. Nix/Guix specific store check (as a last resort)
for (const appId of appIds) {
let execPath = entry?.execString?.replace(/\/bin.*/, "");
if (!execPath)
@@ -112,180 +87,53 @@ Singleton {
return result;
}
signal getDefaultAppResult(string mimeType, string desktopFileId, string callbackId)
signal getAppsForMimeResult(string mimeType, var appIds, string callbackId)
// Set default app for a MIME type
Component {
id: gioSetDefaultApp
function setDefaultApp(mimeType, desktopFileId, callbackId = "") {
setDefaultAppForMimes([mimeType], desktopFileId, callbackId);
}
Process {
property string targetMimeType: ""
property string targetDesktopFileId: ""
property string callbackId: ""
// Check if the app actually supports the MIME type before setting it as default
// This uses a shell script
command: ["sh", "-c", `
apps=$(gio mime "${targetMimeType}" 2>/dev/null | grep -v "^Default" | awk '{print $1}')
if echo "$apps" | grep -Fxq "${targetDesktopFileId}"; then
xdg-mime default "${targetDesktopFileId}" "${targetMimeType}"
gio mime "${targetMimeType}" "${targetDesktopFileId}"
fi
`]
onExited: (exitCode, exitStatus) => {
const success = (exitCode === 0)
if (!success) {
log.error("DesktopService: failed to set default app for", targetMimeType, "to", targetDesktopFileId, "(exit code:", exitCode + ")")
}
root._processDefaultAppQueue()
destroy()
function setDefaultAppForMimes(mimeTypes, desktopFileId, callbackId = "") {
if (!desktopFileId.endsWith(".desktop")) {
desktopFileId += ".desktop";
}
const filtered = (mimeTypes || []).filter(m => m && m.length > 0);
if (filtered.length === 0)
return;
DMSService.sendRequest("mime.setDefaults", {
"mimeTypes": filtered,
"desktopId": desktopFileId
}, response => {
if (response.error) {
log.warn("DesktopService.setDefaultApp failed:", response.error, "mimes:", filtered, "app:", desktopFileId);
}
}
}
function setDefaultApp(mimeType, desktopFileId, callbackId = "") {
// Add .desktop in case it's missing, xdg-mime needs it
if (!desktopFileId.endsWith(".desktop")) {
desktopFileId += ".desktop";
}
// Queue the request to avoid race conditions
_setDefaultAppQueue.push({
mimeType: mimeType,
desktopFileId: desktopFileId,
callbackId: callbackId
})
// Start processing the queue if not already running
if (!_isProcessingQueue) {
_processDefaultAppQueue()
}
}
function _processDefaultAppQueue() {
if (_setDefaultAppQueue.length === 0) {
_isProcessingQueue = false;
return;
}
_isProcessingQueue = true;
const request = _setDefaultAppQueue.shift();
const proc = gioSetDefaultApp.createObject(root, {
targetMimeType: request.mimeType,
targetDesktopFileId: request.desktopFileId,
callbackId: request.callbackId,
running: true
})
if (!proc) {
log.warn("DesktopService: couldn't create process for", request.mimeType, request.desktopFileId)
_processDefaultAppQueue()
}
}
// Get default app for a MIME type
Component {
id: xdgGetDefaultApp
Process {
property string targetMimeType: ""
property string callbackId: ""
stdout: StdioCollector {
onStreamFinished: {
const desktopFileId = text.trim();
root.getDefaultAppResult(targetMimeType, desktopFileId, callbackId);
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text.trim().length > 0) {
log.error("DesktopService: xdg-mime query error:", text, "mime:", targetMimeType)
}
}
}
onExited: (exitCode, exitStatus) => { destroy() }
}
});
}
function getDefaultApp(mimeType, callbackId = "") {
const proc = xdgGetDefaultApp.createObject(root, {
targetMimeType: mimeType,
callbackId: callbackId,
command: ["xdg-mime", "query", "default", mimeType],
running: true
})
if (!proc) {
log.warn("DesktopService: couldn't create process for", mimeType)
}
DMSService.sendRequest("mime.getDefault", {
"mimeType": mimeType
}, response => {
if (response.error) {
log.warn("DesktopService.getDefaultApp failed:", response.error, "mime:", mimeType);
return;
}
const result = response.result || {};
root.getDefaultAppResult(mimeType, result.desktopId || "", callbackId);
});
}
signal getDefaultAppResult(string mimeType, string desktopFileId, string callbackId)
// Get apps that support a MIME type
Component {
id: gioGetAppsForMime
Process {
property string targetMimeType: ""
property string callbackId: ""
stdout: StdioCollector {
onStreamFinished: {
const lines = text.split("\n");
let appIds = [];
let seen = {};
for (let line of lines) {
const trimmed = line.trim();
if (
trimmed &&
trimmed.endsWith(".desktop") &&
!trimmed.startsWith("Default") &&
!trimmed.startsWith("default=")
) {
if (!seen[trimmed]) {
seen[trimmed] = true;
appIds.push(trimmed);
}
}
}
root.getAppsForMimeResult(targetMimeType, appIds, callbackId);
}
}
stderr: StdioCollector {
onStreamFinished: {
if (text.trim().length > 0) {
log.error("DesktopService: gio mime query error:", text, "command:", command, "mime:", targetMimeType);
}
}
}
onExited: (exitCode, exitStatus) => { destroy() }
}
function getAppsForMimeType(mimeType, callbackId = "") {
DMSService.sendRequest("mime.appsForMime", {
"mimeType": mimeType
}, response => {
if (response.error) {
log.warn("DesktopService.getAppsForMimeType failed:", response.error, "mime:", mimeType);
return;
}
const result = response.result || {};
root.getAppsForMimeResult(mimeType, result.desktopIds || [], callbackId);
});
}
function getAppsForMimeType(mimeType, callbackId = "") {
const proc = gioGetAppsForMime.createObject(root, {
targetMimeType: mimeType,
callbackId: callbackId,
command: ["gio", "mime", mimeType],
running: true
});
if (!proc) {
log.warn("DesktopService: couldn't create process for", mimeType)
}
}
signal getAppsForMimeResult(string mimeType, var appIds, string callbackId)
}