// Applications and session startup. import Quickshell import QtQuick import qs.services SettingsPage { id: root objectName: "applications" title: "Applications" lede: "Choose what opens your files and links, and what starts with your session." property string expandedRole: "" readonly property var applications: DesktopEntries.applications.values readonly property var roles: [ { key: "browser", label: "Browser", detail: "Web links and HTML pages", categories: ["webbrowser"], terms: ["browser", "web"] }, { key: "mail", label: "Mail", detail: "Email links", categories: ["email"], terms: ["mail", "email"] }, { key: "files", label: "Files", detail: "Folders and file locations", categories: ["filemanager"], terms: ["file manager", "files"] }, { key: "terminal", label: "Terminal", detail: "Terminal links and command-line handoffs", categories: ["terminalemulator"], terms: ["terminal", "console"] }, { key: "music", label: "Music", detail: "MP3 audio", categories: ["audio", "player"], terms: ["music", "audio player"] }, { key: "images", label: "Images", detail: "PNG images", categories: ["graphics", "viewer"], terms: ["image", "photo", "picture"] }, { key: "video", label: "Video", detail: "MP4 video", categories: ["video", "player"], terms: ["video", "media player"] } ] function desktopId(entry: var): string { const entryId = String(entry?.id ?? ""); return entryId.endsWith(".desktop") ? entryId : entryId + ".desktop"; } function displayName(entry: var): string { return String(entry?.name || entry?.genericName || root.desktopId(entry)); } function currentHandler(role: string): string { return String(DefaultApps.handlers[role] ?? ""); } function currentEntry(role: string): var { const handler = root.currentHandler(role); return root.applications.find(entry => root.desktopId(entry) === handler) ?? null; } function matchesRole(entry: var, role: var): bool { const categories = Array.isArray(entry.categories) ? entry.categories.join(" ").toLowerCase() : String(entry.categories ?? "").toLowerCase(); const metadata = [entry.name, entry.genericName, entry.comment] .map(value => String(value ?? "").toLowerCase()) .join(" "); return role.categories.some(category => categories.includes(category)) || role.terms.some(term => metadata.includes(term)); } function choicesForRole(role: var): var { const choices = root.applications.filter(entry => root.matchesRole(entry, role)); const currentEntry = root.currentEntry(role.key); if (currentEntry && !choices.some(entry => root.desktopId(entry) === root.desktopId(currentEntry))) choices.push(currentEntry); return choices.sort((left, right) => root.displayName(left).localeCompare(root.displayName(right))); } TextRow { visible: DefaultApps.lastError !== "" label: "Could not apply the change" detail: DefaultApps.lastError value: "" divider: false } SettingsCard { title: "Default applications" subtitle: "Open a row to choose from applications that advertise the matching role." Repeater { model: root.roles delegate: Column { id: roleBlock required property var modelData required property int index readonly property var choices: root.choicesForRole(roleBlock.modelData) readonly property var selectedEntry: root.currentEntry(roleBlock.modelData.key) width: parent.width SettingRow { label: roleBlock.modelData.label detail: roleBlock.modelData.detail value: roleBlock.selectedEntry ? root.displayName(roleBlock.selectedEntry) : (root.currentHandler(roleBlock.modelData.key) || "Not set") activatable: roleBlock.choices.length > 0 && !DefaultApps.busy divider: root.expandedRole !== roleBlock.modelData.key && roleBlock.index < root.roles.length - 1 onActivated: { root.expandedRole = root.expandedRole === roleBlock.modelData.key ? "" : roleBlock.modelData.key; } } Column { width: parent.width visible: root.expandedRole === roleBlock.modelData.key Repeater { model: roleBlock.choices delegate: SettingRow { id: candidateRow required property var modelData required property int index readonly property string candidateId: root.desktopId(candidateRow.modelData) readonly property bool selected: candidateRow.candidateId === root.currentHandler(roleBlock.modelData.key) label: root.displayName(candidateRow.modelData) detail: String(candidateRow.modelData.genericName || candidateRow.modelData.comment || candidateRow.candidateId) value: candidateRow.selected ? "Current" : "" activatable: !candidateRow.selected && !DefaultApps.busy divider: candidateRow.index < roleBlock.choices.length - 1 || roleBlock.index < root.roles.length - 1 onActivated: { DefaultApps.setDefault(roleBlock.modelData.key, candidateRow.candidateId); root.expandedRole = ""; } } } } } } } SettingsCard { title: "User autostart" subtitle: "These desktop entries live in your user configuration. Select a row to toggle it." TextRow { visible: DefaultApps.autostartEntries.length === 0 label: "No user autostart entries" detail: "Applications can add entries to ~/.config/autostart." value: "" divider: false } Repeater { model: DefaultApps.autostartEntries delegate: SettingRow { id: autostartRow required property var modelData required property int index label: autostartRow.modelData.name detail: autostartRow.modelData.id value: autostartRow.modelData.enabled ? "Enabled" : "Disabled" activatable: !DefaultApps.busy divider: autostartRow.index < DefaultApps.autostartEntries.length - 1 onActivated: DefaultApps.setAutostart(autostartRow.modelData.id, !autostartRow.modelData.enabled) } } } SettingsCard { title: "Compositor autostart" subtitle: "Panama starts these from Hyprland configuration. They are read-only here." TextRow { visible: DefaultApps.luaAutostartEntries.length === 0 label: "No compositor entries found" detail: "No hl.exec_cmd entries were found in config/dot/hypr/autostart.lua." value: "" divider: false } Repeater { model: DefaultApps.luaAutostartEntries delegate: TextRow { id: luaRow required property var modelData required property int index label: luaRow.modelData.name detail: luaRow.modelData.command value: "Hyprland" divider: luaRow.index < DefaultApps.luaAutostartEntries.length - 1 } } } SettingsCard { title: "Refresh" ActionRow { label: "Reload application settings" detail: "Re-read desktop entries, defaults, and user autostart files" action: DefaultApps.busy ? "Refreshing…" : "Refresh" enabled: !DefaultApps.busy divider: false onTriggered: DefaultApps.refresh() } } }