From 8782fd25339c461228dbb63a921f799e47ed527b Mon Sep 17 00:00:00 2001 From: SomeElse Date: Tue, 9 Jun 2026 04:06:40 +0000 Subject: [PATCH] cleaned and optimized launcher + added actionsview on listview items on launcher. TAB to open it or right click. Added typing animation for Crypto.qml and also anchored the icon on the left so it doesnt keep moving around when changing sizes. --- bar/modules/Crypto.qml | 147 ++++++++++--- components/PopupPanel.qml | 8 +- config/config.qml | 4 +- launcher/Launcher.qml | 420 +++++++++++++++++++++----------------- launcher/launcher.sh | 103 ---------- 5 files changed, 366 insertions(+), 316 deletions(-) delete mode 100755 launcher/launcher.sh diff --git a/bar/modules/Crypto.qml b/bar/modules/Crypto.qml index 64dd323..32f6edc 100644 --- a/bar/modules/Crypto.qml +++ b/bar/modules/Crypto.qml @@ -16,7 +16,9 @@ ModuleChip { id: configWriter } - width: chipRow.implicitWidth + 24 + width: cryptoIcon.visible + ? (12 + cryptoIcon.implicitWidth + 7 + chipRow.implicitWidth + 12) + : (chipRow.implicitWidth + 24) radius: panelRadius chipColor: t.cryptoBg @@ -152,6 +154,7 @@ ModuleChip { if (root.localPairs.length <= 1) return root.activeCoinIndex = (root.activeCoinIndex - 1 + root.localPairs.length) % root.localPairs.length root._applyFromCache() + root._restartTyping() rotateTimer.restart() } @@ -159,6 +162,7 @@ ModuleChip { if (root.localPairs.length <= 1) return root.activeCoinIndex = (root.activeCoinIndex + 1) % root.localPairs.length root._applyFromCache() + root._restartTyping() rotateTimer.restart() } @@ -184,6 +188,51 @@ ModuleChip { sparklineData = entry.sparkline || [] change7d = entry.change7d || 0 hasError = false + root._restartTyping() + } + + property bool _typing: false + property int _typedLabel: 0 + property int _typedValue: 0 + property int _typedIcon: 0 + + function _restartTyping() { + typeAnim.stop() + _typing = true + _typedLabel = 0 + _typedValue = 0 + _typedIcon = 0 + typeAnimLabel.to = (root.coinSymbol + "/" + root.displayCurrency.toUpperCase() + ": ").length + typeAnimValue.to = (root.hasError ? "err" : root.price < 0 ? "…" : root.compactPrice(root.price)).length + typeAnimIcon.to = (root.price >= 0 ? 1 : 0) + typeAnim.start() + } + + SequentialAnimation { + id: typeAnim + onStopped: { root._typing = false } + + NumberAnimation { + id: typeAnimLabel + target: root + property: "_typedLabel" + duration: 200 + easing.type: Easing.Linear + } + NumberAnimation { + id: typeAnimValue + target: root + property: "_typedValue" + duration: 200 + easing.type: Easing.Linear + } + NumberAnimation { + id: typeAnimIcon + target: root + property: "_typedIcon" + duration: 50 + easing.type: Easing.Linear + } } function fetchPrice() { @@ -291,9 +340,26 @@ ModuleChip { onLocalRefreshSecChanged: refreshTimer.restart() onLocalRotateSecChanged: rotateTimer.restart() + Text { + id: cryptoIcon + visible: root.localShowIcon + text: "󰿤" + color: t.cryptoIcon + font.pixelSize: 14 + anchors { + left: parent.left + leftMargin: 12 + verticalCenter: parent.verticalCenter + } + } + RowLayout { id: chipRow - anchors.centerIn: parent + anchors { + left: cryptoIcon.visible ? cryptoIcon.right : parent.left + leftMargin: cryptoIcon.visible ? 7 : 12 + verticalCenter: parent.verticalCenter + } spacing: 0 SequentialAnimation on opacity { @@ -310,34 +376,61 @@ ModuleChip { } } - Text { - visible: root.localShowIcon - text: "󰿤" - color: t.cryptoIcon - font.pixelSize: 14 - Layout.alignment: Qt.AlignVCenter - Layout.rightMargin: 7 + Item { + implicitWidth: labelSizer.implicitWidth + implicitHeight: labelSizer.implicitHeight + + Text { + id: labelSizer + text: root.coinSymbol + "/" + root.displayCurrency.toUpperCase() + ": " + color: "transparent" + font.pixelSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + } + + Text { + text: root._typing + ? (root.coinSymbol + "/" + root.displayCurrency.toUpperCase() + ": ").substring(0, root._typedLabel) + : root.coinSymbol + "/" + root.displayCurrency.toUpperCase() + ": " + color: t.cryptoLabel + font.pixelSize: 12 + font.bold: true + verticalAlignment: Text.AlignVCenter + anchors.fill: labelSizer + } + } + + Item { + implicitWidth: valueSizer.implicitWidth + implicitHeight: valueSizer.implicitHeight + + Text { + id: valueSizer + text: root.hasError ? "err" : root.price < 0 ? "…" : root.compactPrice(root.price) + color: "transparent" + font.pixelSize: 13 + font.bold: true + verticalAlignment: Text.AlignVCenter + } + + Text { + text: root._typing + ? (root.hasError ? "err" : root.price < 0 ? "…" : root.compactPrice(root.price)).substring(0, root._typedValue) + : root.hasError ? "err" : root.price < 0 ? "…" : root.compactPrice(root.price) + color: t.cryptoValue + font.pixelSize: 13 + font.bold: true + verticalAlignment: Text.AlignVCenter + anchors.fill: valueSizer + } } Text { - text: root.coinSymbol + "/" + root.displayCurrency.toUpperCase() + ": " - color: t.cryptoLabel - font.pixelSize: 12 - font.bold: true - verticalAlignment: Text.AlignVCenter - } - - Text { - text: root.hasError ? "err" : root.price < 0 ? "…" : root.compactPrice(root.price) - color: t.cryptoValue - font.pixelSize: 13 - font.bold: true - verticalAlignment: Text.AlignVCenter - } - - Text { - visible: root.price >= 0 - text: root.change24h >= 0 ? "▲" : "▼" + visible: root._typing || root.price >= 0 + text: root._typing + ? (root.price >= 0 ? (root.change24h >= 0 ? "▲" : "▼") : "").substring(0, root._typedIcon) + : (root.price >= 0 ? (root.change24h >= 0 ? "▲" : "▼") : "") color: root.change24h >= 0 ? (t.cryptoUp) : (t.cryptoDown) font.pixelSize: 9 font.bold: true diff --git a/components/PopupPanel.qml b/components/PopupPanel.qml index 989855d..281d022 100644 --- a/components/PopupPanel.qml +++ b/components/PopupPanel.qml @@ -21,7 +21,13 @@ PanelWindow { property bool popupOpen: false property real popupCenterX: -1 - function _doOpen() { hideTimer.stop(); visible = true; popupOpen = true } + function _doOpen() { + hideTimer.stop() + if (chipRoot) { + popupCenterX = chipRoot.mapToItem(null, 0, 0).x + chipRoot.width / 2 + } + visible = true; popupOpen = true + } function open() { _doOpen() } diff --git a/config/config.qml b/config/config.qml index 76dda17..a29b9ae 100644 --- a/config/config.qml +++ b/config/config.qml @@ -118,8 +118,6 @@ QtObject { // ──────────────────────────────────────────────────────────────────────── // LAUNCHER // ──────────────────────────────────────────────────────────────────────── - property string launcherScript: Quickshell.env("HOME") + "/.config/quickshell/launcher/launcher.sh" - property string launcherCacheDir: "/tmp/qs_launcher" property bool launcherCloseOnClickOutside: true property bool launcherSelectFirst: false property bool launcherMipmapIcons: false @@ -142,7 +140,7 @@ QtObject { // // This list is the *default*; pairs can be added/removed live in the // Settings popup and those changes are persisted across restarts. - property var cryptoPairs: [{ coin: "bitcoin", currency: "usd" }, { coin: "ethereum", currency: "eur" }, { coin: "monero", currency: "usd" }, { coin: "bitcoin", currency: "eur" }, { coin: "bitcoin", currency: "brl" }, { coin: "ethereum", currency: "usd" }] + property var cryptoPairs: [{ coin: "bitcoin", currency: "usd" }, { coin: "ethereum", currency: "eur" }, { coin: "monero", currency: "usd" }, { coin: "bitcoin", currency: "eur" }, { coin: "bitcoin", currency: "brl" }, { coin: "ethereum", currency: "usd" }, { coin: "zcash", currency: "usd" }] // ── Compact label decimals ──────────────────────────────────────────────── // 0 → "80k" diff --git a/launcher/Launcher.qml b/launcher/Launcher.qml index e701877..0a7ded0 100644 --- a/launcher/Launcher.qml +++ b/launcher/Launcher.qml @@ -3,8 +3,8 @@ import QtQuick.Layouts import QtQuick.Controls import Quickshell import Quickshell.Wayland -import Quickshell.Io import Quickshell.Hyprland +import Quickshell.Io import "../config" as Cfg import "../components" @@ -21,6 +21,78 @@ PanelWindow { readonly property int launcherGap: 12 + property var allItems: [] + property var favoriteIds: [] + property var accessCounts: ({}) + property bool _loaded: false + + readonly property string _statePath: Quickshell.env("HOME") + "/.config/quickshell/launcher/launcher_state.json" + + function buildItems() { + const apps = DesktopEntries.applications.values + const items = [] + for (let i = 0; i < apps.length; i++) { + const e = apps[i] + items.push({ + name: e.name, + exec: e.execString, + id: e.id, + icon: e.icon, + entry: e, + actions: e.actions + }) + } + items.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())) + root.allItems = items + } + + Component.onCompleted: buildItems() + + Connections { + target: DesktopEntries + function onApplicationsChanged() { root.buildItems() } + } + + FileView { + id: stateFile + path: root._statePath + printErrors: false + watchChanges: true + onFileChanged: reload() + onAdapterUpdated: writeAdapter() + adapter: JsonAdapter { + id: stateAdapter + property list favorites: [] + property var access: ({}) + } + onLoaded: { + root.favoriteIds = stateAdapter.favorites + root.accessCounts = stateAdapter.access + root._loaded = true + } + onLoadFailed: { + root._loaded = true + } + } + + function toggleFavorite(itemId) { + const arr = root.favoriteIds.slice() + const idx = arr.indexOf(itemId) + if (idx >= 0) + arr.splice(idx, 1) + else + arr.push(itemId) + root.favoriteIds = arr + stateAdapter.favorites = root.favoriteIds + } + + function incrementAccess(itemId) { + const counts = Object.assign({}, root.accessCounts) + counts[itemId] = (counts[itemId] || 0) + 1 + root.accessCounts = counts + stateAdapter.access = root.accessCounts + } + GlobalShortcut { id: launcherShortcut name: "toggle" @@ -30,13 +102,14 @@ PanelWindow { property bool _isOpen: false readonly property bool isOpen: _isOpen property bool _keyboardNav: false - property var allItems: [] - property bool loading: false - property int lastCacheUpdate: 0 - property var favoriteIds: [] - property var accessCounts: ({}) - readonly property string statePath: Quickshell.env("HOME") + "/.config/quickshell/launcher/launcher_state.json" + property string _expandingActions: "" + property string _savedExpandId: "" + property real _savedContentY: 0 + property bool _pendingRestore: false + property bool _inRestore: false + + property string expandedItemId: "" readonly property int defaultIndex: Cfg.Config.launcherSelectFirst ? 0 : -1 @@ -52,15 +125,33 @@ PanelWindow { let rest = filtered.filter(i => !root.favoriteIds.includes(i.id)) rest.sort((a, b) => (root.accessCounts[b.id] || 0) - (root.accessCounts[a.id] || 0)) const result = [] + + function pushItem(item) { + result.push(Object.assign({}, item, { isHeader: false, isAction: false })) + if (root.expandedItemId && item.id === root.expandedItemId && item.actions && item.actions.length > 0) { + for (let j = 0; j < item.actions.length; j++) { + const a = item.actions[j] + result.push({ + isHeader: false, + isAction: true, + name: a.name, + id: item.id + ":" + a.id, + icon: a.icon, + actionObj: a + }) + } + } + } + if (favs.length > 0) { result.push({ isHeader: true, section: "Favorites" }) for (let i = 0; i < favs.length; i++) - result.push(Object.assign({}, favs[i], { isHeader: false })) + pushItem(favs[i]) } if (rest.length > 0) { result.push({ isHeader: true, section: "All Applications" }) for (let i = 0; i < rest.length; i++) - result.push(Object.assign({}, rest[i], { isHeader: false })) + pushItem(rest[i]) } return result } @@ -88,16 +179,16 @@ PanelWindow { closeTimer.stop() visible = true _isOpen = true + expandedItemId = "" searchInput.text = "" resultsList.currentIndex = root.defaultIndex Qt.callLater(() => resultsList.positionViewAtBeginning()) - loadItems() - loadState() Qt.callLater(() => searchInput.forceActiveFocus()) } function close() { _isOpen = false + expandedItemId = "" closeTimer.restart() } @@ -108,108 +199,18 @@ PanelWindow { } } - function loadItems() { - loading = true - var now = Date.now() - var staleMs = 300000 - if (now - lastCacheUpdate < staleMs) { - readCache() - return - } - readCache() - lastCacheUpdate = now - updateProc.command = [Cfg.Config.launcherScript, "--update-only"] - updateProc.running = true - } - - function readCache() { - const path = Cfg.Config.launcherCacheDir + "/drun.txt" - dataProc.rawOutput = "" - dataProc.command = ["cat", path] - dataProc.running = true - } - - function shellQuote(str) { - return "'" + str.replace(/'/g, "'\\''") + "'" - } - - function parseExec(str) { - const args = [] - let current = "" - let inSingle = false - let inDouble = false - - for (let i = 0; i < str.length; i++) { - const ch = str[i] - - if (inSingle) { - if (ch === "'") inSingle = false - else current += ch - } else if (inDouble) { - if (ch === '"') inDouble = false - else current += ch - } else if (ch === "'") { - inSingle = true - } else if (ch === '"') { - inDouble = true - } else if (/\s/.test(ch)) { - if (current.length > 0) { - args.push(current) - current = "" - } - } else { - current += ch - } - } - if (current.length > 0) args.push(current) - return args - } - - function loadState() { - stateReadProc.command = ["sh", "-c", "cat " + shellQuote(statePath) + " 2>/dev/null || true"] - stateReadProc.running = true - } - - function saveState() { - const data = JSON.stringify({ favorites: favoriteIds, access: accessCounts }) - stateWriteProc.command = ["sh", "-c", - "printf '%s' " + shellQuote(data) + " > " + shellQuote(statePath)] - stateWriteProc.running = true - } - - function toggleFavorite(itemId) { - const arr = favoriteIds.slice() - const idx = arr.indexOf(itemId) - if (idx >= 0) { - arr.splice(idx, 1) - } else { - arr.push(itemId) - } - favoriteIds = arr - saveState() - } - - function incrementAccess(itemId) { - const counts = Object.assign({}, accessCounts) - counts[itemId] = (counts[itemId] || 0) + 1 - accessCounts = counts - saveState() - } - function launch(item) { - if (!item || !item.exec) return + if (!item || !item.entry) return + item.entry.execute() + console.log("Launching: " + item.name) + root.incrementAccess(item.id) + close() + } - let cleanExec = item.exec.replace(/%[fFuUik]/g, "").trim() - const logPath = "/tmp/qml_launch.log" - - const args = parseExec(cleanExec) - const cmd = args.map(a => shellQuote(a)).join(" ") + " > " + shellQuote(logPath) + " 2>&1 < /dev/null &" - - launchProc.command = ["sh", "-c", cmd] - launchProc.running = true - - console.log("Launching: " + cleanExec) - incrementAccess(item.id) + function executeAction(item) { + if (!item || !item.actionObj) return + item.actionObj.execute() + console.log("Executing action: " + item.name) close() } @@ -228,61 +229,9 @@ PanelWindow { PopupHideTimer { id: closeTimer - onTriggered: { - root.visible = false - root.allItems = [] - } + onTriggered: root.visible = false } - Process { - id: updateProc - onRunningChanged: if (!running) root.readCache() - } - - Process { - id: dataProc - property string rawOutput: "" - stdout: SplitParser { onRead: data => dataProc.rawOutput += data + "\n" } - onRunningChanged: { - if (!running && rawOutput) { - const lines = rawOutput.trim().split("\n") - const items = [] - for (let i = 0; i < lines.length; i++) { - const p = lines[i].split("\t") - if (p.length >= 3) items.push({ name: p[0], exec: p[1], id: p[2], icon: p[3] || "" }) - } - root.allItems = items - root.loading = false - } - } - } - - Process { id: launchProc } - - Process { - id: stateReadProc - property string rawData: "" - stdout: SplitParser { onRead: data => stateReadProc.rawData += data + "\n" } - onRunningChanged: { - if (!running) { - if (rawData.trim()) { - try { - const state = JSON.parse(rawData.trim()) - if (Array.isArray(state.favorites)) - root.favoriteIds = state.favorites - if (state.access && typeof state.access === "object") - root.accessCounts = state.access - } catch (e) { - console.log("Failed to parse launcher state: " + e) - } - } - rawData = "" - } - } - } - - Process { id: stateWriteProc } - MouseArea { anchors.fill: parent @@ -355,12 +304,48 @@ PanelWindow { verticalAlignment: TextInput.AlignVCenter selectionColor: t.launcherAccent - onTextChanged: resultsList.currentIndex = root.defaultIndex + onTextChanged: { + resultsList.currentIndex = root.defaultIndex + root.expandedItemId = "" + } Keys.onReturnPressed: { const idx = root.skipHeader(Math.max(0, resultsList.currentIndex), 1) - if (idx >= 0) - root.launch(root.filteredItems[idx]) + if (idx < 0) return + const item = root.filteredItems[idx] + if (item.isAction) + root.executeAction(item) + else + root.launch(item) + } + + Keys.onTabPressed: (event) => { + event.accepted = true + root._keyboardNav = true + const idx = root.skipHeader(Math.max(0, resultsList.currentIndex), 1) + if (idx < 0) return + const item = root.filteredItems[idx] + if (!item || item.isHeader || item.isAction) return + root._savedContentY = resultsList.contentY + root._savedExpandId = item.id + root._expandingActions = item.id + if (root.expandedItemId === item.id) { + root._pendingRestore = true + root.expandedItemId = "" + } else if (item.actions && item.actions.length > 0) { + root._pendingRestore = true + root.expandedItemId = item.id + } + Qt.callLater(() => { + for (let i = 0; i < resultsList.count; i++) { + const it = root.filteredItems[i] + if (it.id === item.id && !it.isAction && !it.isHeader) { + resultsList.currentIndex = i + resultsList.positionViewAtIndex(i, ListView.Contain) + break + } + } + }) } Keys.onDownPressed: { @@ -432,19 +417,51 @@ PanelWindow { flickableDirection: Flickable.VerticalFlick onModelChanged: { - currentIndex = root.defaultIndex - for (let i = 0; i < count; i++) { - const md = root.filteredItems[i] - if (md && !md.isHeader) { - currentIndex = i - break + if (root._expandingActions !== "") { + root._expandingActions = "" + if (root._savedExpandId !== "") { + Qt.callLater(() => { + for (let i = 0; i < resultsList.count; i++) { + const it = root.filteredItems[i] + if (it.id === root._savedExpandId && !it.isAction && !it.isHeader) { + resultsList.currentIndex = i + resultsList.positionViewAtIndex(i, ListView.Contain) + break + } + } + root._pendingRestore = false + root._savedExpandId = "" + }) + } else { + root._pendingRestore = false } + } else { + currentIndex = root.defaultIndex + for (let i = 0; i < count; i++) { + const md = root.filteredItems[i] + if (md && !md.isHeader && !md.isAction) { + currentIndex = i + break + } + } + Qt.callLater(() => positionViewAtBeginning()) } - Qt.callLater(() => positionViewAtBeginning()) } ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded } + Connections { + target: resultsList + function onContentYChanged() { + if (root._pendingRestore && !root._inRestore && Math.abs(resultsList.contentY - root._savedContentY) > 1) { + root._inRestore = true + resultsList.contentY = root._savedContentY + root._inRestore = false + } + } + } + delegate: Rectangle { + id: itemDelegate width: resultsList.width height: 38 radius: modelData.isHeader ? 0 : 8 @@ -452,6 +469,7 @@ PanelWindow { : (starHovered || hovered) ? t.launcherHover : (resultsList.currentIndex === index && index !== -1 ? t.launcherSelected : "transparent") + clip: modelData.isAction property bool hovered: false property bool starHovered: false @@ -467,11 +485,12 @@ PanelWindow { RowLayout { visible: !modelData.isHeader anchors.fill: parent - anchors.leftMargin: 12 + anchors.leftMargin: modelData.isAction ? 28 : 12 anchors.rightMargin: 12 spacing: 12 Item { + visible: !modelData.isAction width: 22; height: 22 Layout.alignment: Qt.AlignVCenter @@ -496,14 +515,31 @@ PanelWindow { } Text { - text: modelData.name - color: t.launcherText + visible: modelData.isAction + text: "›" + color: t.launcherDim font.pixelSize: 14 + Layout.alignment: Qt.AlignVCenter + } + + Text { + text: modelData.name + color: modelData.isAction ? t.launcherDim : t.launcherText + font.pixelSize: modelData.isAction ? 13 : 14 Layout.fillWidth: true elide: Text.ElideRight } + Text { + visible: !modelData.isAction && modelData.actions && modelData.actions.length > 0 + text: root.expandedItemId === modelData.id ? "▲" : "▼" + color: t.launcherDim + font.pixelSize: 9 + Layout.alignment: Qt.AlignVCenter + } + Item { + visible: !modelData.isAction width: 26; height: 26 Layout.alignment: Qt.AlignVCenter @@ -525,20 +561,40 @@ PanelWindow { } MouseArea { + acceptedButtons: Qt.LeftButton | Qt.RightButton anchors.fill: parent hoverEnabled: true onEntered: parent.hovered = true onExited: { parent.hovered = false; parent.starHovered = false } onPositionChanged: mouse => { - parent.starHovered = !modelData.isHeader && (mouse.x >= width - 46) + parent.starHovered = !modelData.isHeader && !modelData.isAction && (mouse.x >= width - 46) } onClicked: mouse => { if (modelData.isHeader) return - if (mouse.x >= width - 46) { - root.toggleFavorite(modelData.id) - } else { - root.launch(modelData) + + if (mouse.button === Qt.RightButton) { + if (!modelData.isAction && modelData.actions && modelData.actions.length > 0) { + root._savedContentY = resultsList.contentY + root._savedExpandId = modelData.id + root._expandingActions = modelData.id + root._pendingRestore = true + if (root.expandedItemId === modelData.id) + root.expandedItemId = "" + else + root.expandedItemId = modelData.id + } + return } + + if (modelData.isAction) { + root.executeAction(modelData) + return + } + + if (mouse.x >= width - 46) + root.toggleFavorite(modelData.id) + else + root.launch(modelData) } } } diff --git a/launcher/launcher.sh b/launcher/launcher.sh deleted file mode 100755 index a997272..0000000 --- a/launcher/launcher.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash - -CACHE_DIR="/tmp/qs_launcher" - -DRUN_CACHE="$CACHE_DIR/drun.txt" -RUN_CACHE="$CACHE_DIR/run.txt" - -mkdir -p "$CACHE_DIR" - - -get_desktop_files() { - IFS=':' read -ra DIRS <<< \ - "${XDG_DATA_DIRS:-/usr/local/share:/usr/share}" - - for dir in "${DIRS[@]}"; do - APP_DIR="$dir/applications" - - [ -d "$APP_DIR" ] || continue - - find "$APP_DIR" \ - -maxdepth 1 \ - -name "*.desktop" \ - 2>/dev/null - done | while read -r f; do - - name=$( - grep -m1 '^Name=' "$f" \ - | cut -d= -f2- - ) - - exec=$( - grep -m1 '^Exec=' "$f" \ - | cut -d= -f2- \ - | sed 's/%[fFuUik]//g' - ) - - type=$( - grep -m1 '^Type=' "$f" \ - | cut -d= -f2- - ) - - nodisplay=$( - grep -m1 '^NoDisplay=' "$f" \ - | cut -d= -f2- \ - | tr '[:upper:]' '[:lower:]' - ) - - icon=$( - grep -m1 '^Icon=' "$f" \ - | cut -d= -f2- - ) - - [[ "$type" == "Application" ]] || continue - [[ "$nodisplay" != "true" ]] || continue - [[ -n "$name" ]] || continue - - printf "%s\t%s\t%s\t%s\n" \ - "$name" \ - "$exec" \ - "$(basename "$f" .desktop)" \ - "$icon" - done -} - - -get_binaries() { - compgen -c \ - | sort -u \ - | head -600 \ - | awk '{ - print $1 "\t" $1 "\t" $1 - }' -} - - -sort_and_dedup() { - sort -f -t$'\t' -k1,1 \ - | awk -F'\t' '!seen[$1]++' -} - - -build_drun() { - get_desktop_files | sort_and_dedup -} - -build_run() { - { get_desktop_files; get_binaries; } | sort_and_dedup -} - - -update_cache() { - build_drun > "$DRUN_CACHE.tmp" - build_run > "$RUN_CACHE.tmp" - - mv "$DRUN_CACHE.tmp" "$DRUN_CACHE" - mv "$RUN_CACHE.tmp" "$RUN_CACHE" -} - - -if [ "$1" == "--update-only" ]; then - update_cache - exit 0 -fi