diff --git a/Bar/Modules/Applauncher.qml b/Bar/Modules/Applauncher.qml index 9f4e88d..4536c15 100644 --- a/Bar/Modules/Applauncher.qml +++ b/Bar/Modules/Applauncher.qml @@ -11,6 +11,23 @@ import "../../Helpers/Fuzzysort.js" as Fuzzysort PanelWithOverlay { id: appLauncherPanel WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand + + function isPinned(app) { + return app && app.execString && Settings.settings.pinnedExecs.indexOf(app.execString) !== -1; + } + function togglePin(app) { + if (!app || !app.execString) return; + var arr = Settings.settings.pinnedExecs ? Settings.settings.pinnedExecs.slice() : []; + var idx = arr.indexOf(app.execString); + if (idx === -1) { + arr.push(app.execString); + } else { + arr.splice(idx, 1); + } + Settings.settings.pinnedExecs = arr; + root.updateFilter(); + } + function showAt() { appLauncherPanelRect.showAt(); } @@ -126,7 +143,22 @@ PanelWithOverlay { return r.obj; })); } - root.filteredApps = results; + // Pinning logic: split into pinned and unpinned + var pinned = []; + var unpinned = []; + for (var i = 0; i < results.length; ++i) { + var app = results[i]; + if (app.execString && Settings.settings.pinnedExecs.indexOf(app.execString) !== -1) { + pinned.push(app); + } else { + unpinned.push(app); + } + } + // Sort pinned alphabetically + pinned.sort(function(a, b) { + return a.name.toLowerCase().localeCompare(b.name.toLowerCase()); + }); + root.filteredApps = pinned.concat(unpinned); root.selectedIndex = 0; } function selectNext() { @@ -276,10 +308,14 @@ PanelWithOverlay { Rectangle { anchors.fill: parent - color: hovered || isSelected ? Theme.accentPrimary : "transparent" + color: (hovered || isSelected) + ? Theme.accentPrimary + : (appLauncherPanel.isPinned(modelData) ? Theme.surfaceVariant : "transparent") radius: 12 - border.color: hovered || isSelected ? Theme.accentPrimary : "transparent" - border.width: hovered || isSelected ? 2 : 0 + border.color: appLauncherPanel.isPinned(modelData) + ? "transparent" + : (hovered || isSelected ? Theme.accentPrimary : "transparent") + border.width: appLauncherPanel.isPinned(modelData) ? 0 : (hovered || isSelected ? 2 : 0) Behavior on color { ColorAnimation { duration: 120 @@ -331,7 +367,7 @@ PanelWithOverlay { spacing: 1 Text { text: modelData.name - color: hovered || isSelected ? Theme.onAccent : Theme.textPrimary + color: (hovered || isSelected) ? Theme.onAccent : (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textPrimary) font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall font.bold: hovered || isSelected @@ -341,7 +377,7 @@ PanelWithOverlay { } Text { text: modelData.isCalculator ? (modelData.expr + " = " + modelData.result) : (modelData.comment || modelData.genericName || "No description available") - color: hovered || isSelected ? Theme.onAccent : Theme.textSecondary + color: (hovered || isSelected) ? Theme.onAccent : (appLauncherPanel.isPinned(modelData) ? Theme.textSecondary : Theme.textSecondary) font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeCaption font.italic: !(modelData.comment || modelData.genericName) @@ -358,9 +394,14 @@ PanelWithOverlay { text: modelData.isCalculator ? "content_copy" : "chevron_right" font.family: "Material Symbols Outlined" font.pixelSize: Theme.fontSizeBody - color: hovered || isSelected ? Theme.onAccent : Theme.textSecondary + color: (hovered || isSelected) + ? Theme.onAccent + : (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textSecondary) verticalAlignment: Text.AlignVCenter + Layout.rightMargin: 8 // Add margin to separate from star } + // Add a spacing item between chevron and star + Item { width: 8; height: 1 } } Rectangle { @@ -374,7 +415,14 @@ PanelWithOverlay { id: mouseArea anchors.fill: parent hoverEnabled: true + acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: { + // Prevent app launch if click is inside pinArea + if (pinArea.containsMouse) return; + if (mouse.button === Qt.RightButton) { + appLauncherPanel.togglePin(modelData); + return; + } ripple.opacity = 0.18; rippleNumberAnimation.start(); root.selectedIndex = index; @@ -401,6 +449,37 @@ PanelWithOverlay { color: Theme.outline opacity: index === appList.count - 1 ? 0 : 0.10 } + // Pin/Unpin button (move to last child for stacking) + Item { + id: pinArea + width: 28; height: 28 + z: 100 // Ensure above everything else + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + MouseArea { + anchors.fill: parent + preventStealing: true + z: 100 + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton | Qt.RightButton + propagateComposedEvents: false + onClicked: { + appLauncherPanel.togglePin(modelData); + event.accepted = true; + } + } + Text { + anchors.centerIn: parent + text: "star" + font.family: "Material Symbols Outlined" + font.pixelSize: Theme.fontSizeSmall + color: (parent.MouseArea.containsMouse || hovered || isSelected) + ? Theme.onAccent + : (appLauncherPanel.isPinned(modelData) ? Theme.textPrimary : Theme.textDisabled) + verticalAlignment: Text.AlignVCenter + } + } } } } diff --git a/Bar/Modules/AudioDeviceSelector.qml b/Bar/Modules/AudioDeviceSelector.qml index 6d14974..4c40ed9 100644 --- a/Bar/Modules/AudioDeviceSelector.qml +++ b/Bar/Modules/AudioDeviceSelector.qml @@ -226,7 +226,9 @@ PanelWithOverlay { function sinkNodes() { let nodes = Pipewire.nodes && Pipewire.nodes.values - ? Pipewire.nodes.values.filter(function(n) { return n.isSink && n.audio }) + ? Pipewire.nodes.values.filter(function(n) { + return n.isSink && n.audio && n.isStream === false; + }) : []; if (Pipewire.defaultAudioSink) { nodes = nodes.slice().sort(function(a, b) { @@ -239,7 +241,9 @@ PanelWithOverlay { } function sourceNodes() { let nodes = Pipewire.nodes && Pipewire.nodes.values - ? Pipewire.nodes.values.filter(function(n) { return !n.isSink && n.audio }) + ? Pipewire.nodes.values.filter(function(n) { + return !n.isSink && n.audio && n.isStream === false; + }) : []; if (Pipewire.defaultAudioSource) { nodes = nodes.slice().sort(function(a, b) { diff --git a/Settings/Settings.qml b/Settings/Settings.qml index 426bc39..95fe8b3 100644 --- a/Settings/Settings.qml +++ b/Settings/Settings.qml @@ -7,7 +7,7 @@ import qs.Services Singleton { property string shellName: "Noctalia" - property string settingsDir: Quickshell.env("HOME") + "/.config/" + shellName + "/" + property string settingsDir: (Quickshell.env("XDG_CONFIG_HOME") || Quickshell.env("HOME") + "/.config") + "/" + shellName + "/" property string settingsFile: settingsDir + "Settings.json" property var settings: settingAdapter @@ -57,6 +57,7 @@ Singleton { property bool reverseDayMonth: false property bool use12HourClock: false property bool dimPanels: true + property var pinnedExecs: [] // Added for AppLauncher pinned apps } }