// qs/Services/Files.qml pragma Singleton import QtQuick import Quickshell import Quickshell.Io import ZShell.Config Singleton { id: root property list results: [] property string _pendingQuery: "" property int _generation: 0 readonly property var _imageExts: ({ "png": true, "jpg": true, "jpeg": true, "gif": true, "bmp": true, "webp": true, "svg": true, "tiff": true, "tif": true, "ico": true, "heic": true, "avif": true }) // freedesktop mimetype-icon names; extend as needed readonly property var _extIcons: ({ // images "png": "image-x-generic", "jpg": "image-x-generic", "jpeg": "image-x-generic", "gif": "image-x-generic", "bmp": "image-x-generic", "webp": "image-x-generic", "svg": "image-x-generic", "tiff": "image-x-generic", "tif": "image-x-generic", "ico": "image-x-generic", "heic": "image-x-generic", "avif": "image-x-generic", // documents "pdf": "application-pdf", "doc": "x-office-document", "docx": "x-office-document", "odt": "x-office-document", "txt": "text-x-generic", "md": "text-x-generic", "xls": "x-office-spreadsheet", "xlsx": "x-office-spreadsheet", "ods": "x-office-spreadsheet", "ppt": "x-office-presentation", "pptx": "x-office-presentation", "odp": "x-office-presentation", // archives "zip": "package-x-generic", "tar": "package-x-generic", "gz": "package-x-generic", "xz": "package-x-generic", "7z": "package-x-generic", "rar": "package-x-generic", // audio/video "mp3": "audio-x-generic", "flac": "audio-x-generic", "wav": "audio-x-generic", "ogg": "audio-x-generic", "mp4": "video-x-generic", "mkv": "video-x-generic", "webm": "video-x-generic", "avi": "video-x-generic", "mov": "video-x-generic", // code "js": "text-x-script", "ts": "text-x-script", "py": "text-x-script", "sh": "text-x-script", "qml": "text-x-script", "cpp": "text-x-c++src", "c": "text-x-csrc", "h": "text-x-chdr", "json": "application-json", "html": "text-html", "css": "text-css" }) function showInFolder(path: string): void { const explorer = Config.general.apps.explorer; let args = []; if (explorer.includes("dolphin")) args = [...explorer, "--select", path]; else args = [...explorer, path]; if (Config.launcher.uwsm) Quickshell.execDetached(["app2unit", "--", ...args]); else Quickshell.execDetached([...args]); } function launch(command: list): void { if (Config.launcher.uwsm) Quickshell.execDetached(["app2unit", "--"].concat(command)); else Quickshell.execDetached(command); } function transformSearch(search: string): string { return search.slice(Config.launcher.actionPrefix.length + "files ".length); } function query(search: string): list { search = transformSearch(search); if (search !== _pendingQuery) { _pendingQuery = search; debounceTimer.restart(); } return results; } function _extOf(name: string): string { const i = name.lastIndexOf("."); // no dot, or dotfile with no real extension (".bashrc" -> treat as no ext) if (i <= 0) return ""; return name.slice(i + 1).toLowerCase(); } function _iconFor(name: string, isDir: bool): string { if (isDir) return "folder"; const ext = _extOf(name); return _extIcons[ext] ?? "text-x-generic"; } function _buildEntry(path: string): var { const name = path.split("/").pop(); const ext = _extOf(name); // trailing slash from plocate's directory output (if -d/dir results ever appear) const isDir = path.endsWith("/"); return { path: path, name: name, icon: _iconFor(name, isDir), isImage: !isDir && (_imageExts[ext] ?? false) }; } Timer { id: debounceTimer interval: 120 onTriggered: root._runQuery(root._pendingQuery) } function _runQuery(search: string): void { _generation++; if (!search) { proc.running = false; results = []; return; } if (proc.running) proc.running = false; proc.buffer = []; proc.myGeneration = _generation; proc.command = ["plocate", "-i", "-b", "--limit", "50", search]; proc.running = true; } Process { id: proc property var buffer: [] property int myGeneration: 0 stdout: SplitParser { onRead: line => proc.buffer.push(root._buildEntry(line)) } onRunningChanged: { if (!running && myGeneration === root._generation) root.results = buffer; } } }