import QtQuick import Quickshell import "launch.js" as Launch Item { id: root property var launchRecords: [] property var lastInteraction: ({}) readonly property var apps: Launch.buildApps(DesktopEntries.applications ? DesktopEntries.applications.values : []) readonly property bool appScanDone: true readonly property string appScanError: "" function stateJson() { return JSON.stringify({ "launchRecords": launchRecords, "lastInteraction": lastInteraction, "appCount": apps.length, "appScanDone": appScanDone, "appScanError": appScanError }); } function rememberActivation(record, executed) { const entry = { "kind": "activate", "record": record, "executed": !!executed }; const rows = launchRecords.slice(Math.max(0, launchRecords.length - 19)); rows.push(record); launchRecords = rows; lastInteraction = entry; return stateJson(); } function rowFromJson(rowJson) { if (typeof rowJson === "string") { try { return JSON.parse(rowJson); } catch (error) { return { "id": "invalid", "title": "Invalid launcher row", "provider": "error", "error": String(error) }; } } return rowJson || ({}); } function sliceForRow(row) { return row.terminal ? "work.slice" : "app-graphical.slice"; } function launchRecord(rowJson) { const row = rowFromJson(rowJson); const title = String(row.title || row.id || "app"); const id = String(row.id || row.desktopId || title); return JSON.stringify({ "id": id, "label": title, "kind": "app", "provider": "app", "desktopId": row.desktopId || "", "desktopPath": row.desktopPath || "", "argv": row.execArgv || [], "workingDirectory": row.workingDirectory || "", "environment": {}, "slice": sliceForRow(row), "startupNotify": true, "windowMatching": { "desktopId": row.desktopId || "", "title": title, "appId": row.desktopId ? String(row.desktopId).replace(/\.desktop$/, "") : "" }, "osdFeedback": `app:${title}`, "route": "desktop-entry", "terminal": !!row.terminal }); } function execRecord(record) { if (!record.argv || record.argv.length === 0) return false; const selector = record.slice === "work.slice" ? "w" : "a"; Quickshell.execDetached(["uwsm", "app", "-s", selector, "--"].concat(record.argv)); return true; } function activate(rowJson) { const record = JSON.parse(launchRecord(rowJson)); return rememberActivation(record, execRecord(record)); } function activateAction(rowJson, index) { const row = rowFromJson(rowJson); const action = (row.actions || [])[Number(index) || 0]; if (!action) { lastInteraction = { "kind": "activate-missing-action", "row": row.id || row.title || "app", "index": Number(index) || 0 }; return stateJson(); } const record = JSON.parse(launchRecord(row)); record.id = `${record.id}:${action.id || action.title}`; record.label = `${record.label}: ${action.title || action.id}`; record.argv = action.execArgv || []; record.desktopPath = ""; record.osdFeedback = `app-action:${record.label}`; return rememberActivation(record, execRecord(record)); } function activateFirst(query) { const result = JSON.parse(search(query)); if (!result.results || result.results.length === 0) { lastInteraction = { "kind": "activate-empty", "query": String(query) }; return stateJson(); } return activate(JSON.stringify(result.results[0])); } function rowById(id) { const wanted = String(id); for (let i = 0; i < apps.length; i++) { if (apps[i].id === wanted || apps[i].desktopId === wanted) return Launch.shapeRow(Object.assign({ "route": "desktop-entry" }, apps[i]), "app", 1500); } return null; } function activateId(id) { const row = rowById(id); if (!row) { lastInteraction = { "kind": "activate-missing-id", "id": String(id) }; return stateJson(); } return activate(JSON.stringify(row)); } function hotkey(id) { const name = String(id); let record = { "id": name, "label": name, "kind": "hotkey", "provider": "hotkey", "argv": ["nuguctl", "hotkey", name], "slice": "session-graphical.slice", "osdFeedback": name }; if (name === "open-launcher") record.argv = ["qs", "ipc", "-c", "nuguland", "call", "nuguland", "openLauncher"]; lastInteraction = { "kind": "hotkey", "record": record }; return JSON.stringify(lastInteraction); } function search(raw) { const ranked = Launch.rankResults(raw, { "apps": apps }); return JSON.stringify({ "query": raw, "providerIntent": ranked.providerIntent, "selectedIndex": ranked.selectedIndex, "results": ranked.results }); } function selfTest() { const cases = []; function ok(name, cond) { cases.push({ "name": name, "pass": !!cond }); } try { ok("intent-app", Launch.prefixIntent("firefox").intent === "app"); const exact = Launch.fuzzyScore("firefox", "firefox", 0); const prefix = Launch.fuzzyScore("fire", "firefox", 0); const subseq = Launch.fuzzyScore("frfx", "firefox", 0); ok("fuzzy-order", exact > prefix && prefix > subseq && subseq >= 0); ok("fuzzy-miss", Launch.fuzzyScore("zzz", "firefox", 0) === -1); const built = Launch.buildApps([ { "id": "firefox", "name": "Firefox", "genericName": "Web Browser", "icon": "firefox", "categories": ["Network"], "keywords": ["web", "browser"], "actions": [ { "id": "new-window", "name": "New Window", "command": ["firefox", "--new-window"] } ] } ]); ok("app-details", built[0].details[0].value === "Network" && built[0].keywords[0] === "web"); ok("app-actions", built[0].actions[0].title === "New Window"); const rApp = Launch.rankResults("browser", { "apps": built }); ok("rank-keyword", rApp.selectedIndex === 0 && rApp.results[0].title === "Firefox"); ok("rank-app-only", rApp.results[0].provider === "app"); } catch (error) { ok("threw:" + error, false); } const fail = cases.filter(c => { return !c.pass; }).length; return JSON.stringify({ "pass": cases.length - fail, "fail": fail, "cases": cases }); } }