import QtQuick import QtQuick.Controls import Quickshell import Quickshell.Io Rectangle { id: appLauncher width: 400 height: 600 color: shellRoot.black antialiasing: true property bool isVisible: false property int hoverIndex: 0 property var apps: [] signal requestClose() focus: true Keys.onEscapePressed: requestClose() Keys.onUpPressed: { hoverIndex--; if (hoverIndex < 0) hoverIndex += appListModel.count; listView.positionViewAtIndex(hoverIndex, ListView.Contain); } Keys.onDownPressed: { hoverIndex = (hoverIndex + 1) % appListModel.count; listView.positionViewAtIndex(hoverIndex, ListView.Contain); } Keys.onReturnPressed: { if (hoverIndex >= 0 && hoverIndex < appListModel.count) { launchApp(appListModel.get(hoverIndex).appCommand) console.log(appListModel.get(hoverIndex).appCommand) } } Keys.onEnterPressed: { if (hoverIndex >= 0 && hoverIndex < appListModel.count) { launchApp(appListModel.get(hoverIndex).appCommand) console.log(appListModel.get(hoverIndex).appCommand) } } onIsVisibleChanged: { if (!isVisible) return; hoverIndex = 0; appLauncher.forceActiveFocus(); searchInput.forceActiveFocus(); } Process { id: appLoader running: false command: ["sh", "-c", "~/.config/quickshell/scripts/list-apps.sh"] stdout: SplitParser { onRead: data => { const lines = data.split('\n'); for (const line of lines) { if (line.trim().length === 0) continue; const parts = line.split('|'); if (parts.length < 4) continue; apps.push({ appName: parts[0], appDescription: parts[1], appIcon: parts[2], appCommand: parts[3] }); } applySearch(""); } } onRunningChanged: { if (running) return; appLoader.running = false; } } function loadApps() { appLoader.running = true; } function applySearch(searchString) { appListModel.clear(); for (const app of apps) { if (app.appName.toLowerCase().includes(searchString.toLowerCase())) { appListModel.append(app); } } } Component.onCompleted: { loadApps(); } Column { anchors.fill: parent anchors.margins: 12 spacing: 8 Rectangle { width: parent.width height: 36 color: "transparent" Text { anchors.centerIn: parent text: "Applications" font.family: shellRoot.fontFamily font.pixelSize: 20 font.weight: Font.Medium color: shellRoot.white } Rectangle { width: 28 height: 28 anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter radius: 6 color: closeMouseArea.containsMouse ? shellRoot.red : "transparent" Text { anchors.centerIn: parent text: "✕" font.family: shellRoot.fontFamily font.pixelSize: 16 font.weight: Font.Bold color: closeMouseArea.containsMouse ? shellRoot.white : shellRoot.red } MouseArea { id: closeMouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onClicked: appLauncher.requestClose() } } } Rectangle { width: parent.width height: 36 color: shellRoot.black border.color: shellRoot.green border.width: 1 TextInput { id: searchInput width: parent.width anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: 8 anchors.rightMargin: 8 anchors.verticalCenter: parent.verticalCenter color: "white" font.pixelSize: 14 onTextEdited: { applySearch(searchInput.text); hoverIndex = 0; } } } ListView { id: listView width: parent.width height: parent.height - 88 spacing: 8 clip: true model: ListModel { id: appListModel } delegate: Rectangle { width: listView.width height: 30 color: { if (appLauncher.hoverIndex === index) return shellRoot.lightgray; return shellRoot.gray; } Row { anchors.fill: parent anchors.leftMargin: 12 anchors.rightMargin: 12 anchors.topMargin: 8 anchors.bottomMargin: 8 spacing: 12 Text { width: parent.width - 62 anchors.centerIn: parent text: model.appName font.family: shellRoot.fontFamily font.pixelSize: 14 font.weight: Font.Medium color: { return shellRoot.white; } elide: Text.ElideRight } } MouseArea { id: mouseArea anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor onEntered: { appLauncher.hoverIndex = index; } onClicked: { launchApp(model.appCommand); } } } } } function launchApp(command) { appLauncher.requestClose() // Use a small delay before launching to ensure the window closes properly Qt.callLater(() => { Quickshell.execDetached(["sh", "-c", command + " &"]) }) } }