diff --git a/Commons/Settings.qml b/Commons/Settings.qml index ee50968..a9de196 100644 --- a/Commons/Settings.qml +++ b/Commons/Settings.qml @@ -218,6 +218,9 @@ Singleton { property string visualizerType: "linear" property int volumeStep: 5 property int cavaFrameRate: 60 + // MPRIS controls + property list mprisBlacklist: [] + property string preferredPlayer: "" } // ui diff --git a/Modules/Bar/Widgets/MediaMini.qml b/Modules/Bar/Widgets/MediaMini.qml index dee6c42..a154f12 100644 --- a/Modules/Bar/Widgets/MediaMini.qml +++ b/Modules/Bar/Widgets/MediaMini.qml @@ -50,7 +50,7 @@ Row { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "linear" - && MediaService.isPlaying + && MediaService.isPlaying && MediaService.trackLength > 0 z: 0 sourceComponent: LinearSpectrum { @@ -65,7 +65,7 @@ Row { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "mirrored" - && MediaService.isPlaying + && MediaService.isPlaying && MediaService.trackLength > 0 z: 0 sourceComponent: MirroredSpectrum { @@ -81,7 +81,7 @@ Row { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter active: Settings.data.audio.showMiniplayerCava && Settings.data.audio.visualizerType == "wave" - && MediaService.isPlaying + && MediaService.isPlaying && MediaService.trackLength > 0 z: 0 sourceComponent: WaveSpectrum { diff --git a/Modules/SettingsPanel/Tabs/AudioTab.qml b/Modules/SettingsPanel/Tabs/AudioTab.qml index 065350f..8f1e181 100644 --- a/Modules/SettingsPanel/Tabs/AudioTab.qml +++ b/Modules/SettingsPanel/Tabs/AudioTab.qml @@ -227,6 +227,134 @@ ColumnLayout { Layout.bottomMargin: Style.marginXL * scaling } + // MPRIS Player Preferences + ColumnLayout { + spacing: Style.marginL * scaling + Layout.fillWidth: true + + NText { + text: "MPRIS Player Preferences" + font.pointSize: Style.fontSizeXXL * scaling + font.weight: Style.fontWeightBold + color: Color.mOnSurface + Layout.bottomMargin: Style.marginS * scaling + } + + // Preferred player (persistent) + NTextInput { + label: "Preferred Player" + description: "Substring to match MPRIS player (identity/bus/desktop)." + placeholderText: "e.g. spotify, vlc, mpv" + text: Settings.data.audio.preferredPlayer + onTextChanged: { + Settings.data.audio.preferredPlayer = text + MediaService.updateCurrentPlayer() + } + } + + // Blacklist editor + ColumnLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + + RowLayout { + spacing: Style.marginS * scaling + Layout.fillWidth: true + + NTextInput { + id: blacklistInput + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + label: "Blacklist player" + description: "Substring, e.g. plex, shim, mpv" + placeholderText: "type substring and press +" + } + + // Button aligned to the center of the actual input field + NIconButton { + icon: "add" + Layout.alignment: Qt.AlignBottom + Layout.bottomMargin: blacklistInput.description ? Style.marginS * scaling : 0 + onClicked: { + const val = (blacklistInput.text || "").trim() + if (val !== "") { + const arr = (Settings.data.audio.mprisBlacklist || []) + if (!arr.find(x => String(x).toLowerCase() === val.toLowerCase())) { + Settings.data.audio.mprisBlacklist = [...arr, val] + blacklistInput.text = "" + MediaService.updateCurrentPlayer() + } + } + } + } + } + + // Current blacklist entries + Flow { + Layout.fillWidth: true + Layout.leftMargin: Style.marginS * scaling + spacing: Style.marginS * scaling + + Repeater { + model: Settings.data.audio.mprisBlacklist + delegate: Rectangle { + required property string modelData + // Padding around the inner row + property real pad: Style.marginS * scaling + // Visuals + color: Color.applyOpacity(Color.mOnSurface, "20") + border.color: Color.applyOpacity(Color.mOnSurface, "50") + border.width: Math.max(1, Style.borderS * scaling) + + // Content + RowLayout { + id: chipRow + spacing: Style.marginXS * scaling + anchors.fill: parent + anchors.margins: pad + + NText { + text: modelData + color: Color.mOnSurface + font.pointSize: Style.fontSizeS * scaling + Layout.alignment: Qt.AlignVCenter + Layout.leftMargin: Style.marginS * scaling + } + + NIconButton { + icon: "close" + sizeRatio: 0.8 + Layout.alignment: Qt.AlignVCenter + Layout.rightMargin: Style.marginXS * scaling + onClicked: { + const arr = (Settings.data.audio.mprisBlacklist || []) + const idx = arr.findIndex(x => String(x) === modelData) + if (idx >= 0) { + arr.splice(idx, 1) + Settings.data.audio.mprisBlacklist = arr + MediaService.updateCurrentPlayer() + } + } + } + } + + // Intrinsic size derived from inner row + padding + implicitWidth: chipRow.implicitWidth + pad * 2 + implicitHeight: Math.max(chipRow.implicitHeight + pad * 2, Style.baseWidgetSize * 0.8 * scaling) + radius: Style.radiusM * scaling + } + } + } + } + } + + // Divider + NDivider { + Layout.fillWidth: true + Layout.topMargin: Style.marginXL * scaling + Layout.bottomMargin: Style.marginXL * scaling + } + // Bar Mini Media player ColumnLayout { spacing: Style.marginL * scaling @@ -350,4 +478,4 @@ ColumnLayout { } } } -} +} \ No newline at end of file diff --git a/Modules/SidePanel/Cards/MediaCard.qml b/Modules/SidePanel/Cards/MediaCard.qml index fae3f38..774e031 100644 --- a/Modules/SidePanel/Cards/MediaCard.qml +++ b/Modules/SidePanel/Cards/MediaCard.qml @@ -51,6 +51,7 @@ NBox { Item { Layout.fillWidth: true + Layout.fillHeight: true } } @@ -330,7 +331,7 @@ NBox { } Loader { - active: Settings.data.audio.visualizerType == "linear" + active: Settings.data.audio.visualizerType == "linear" && MediaService.isPlaying && MediaService.trackLength > 0 Layout.alignment: Qt.AlignHCenter sourceComponent: LinearSpectrum { @@ -343,7 +344,7 @@ NBox { } Loader { - active: Settings.data.audio.visualizerType == "mirrored" + active: Settings.data.audio.visualizerType == "mirrored" && MediaService.isPlaying && MediaService.trackLength > 0 Layout.alignment: Qt.AlignHCenter sourceComponent: MirroredSpectrum { @@ -356,7 +357,7 @@ NBox { } Loader { - active: Settings.data.audio.visualizerType == "wave" + active: Settings.data.audio.visualizerType == "wave" && MediaService.isPlaying && MediaService.trackLength > 0 Layout.alignment: Qt.AlignHCenter sourceComponent: WaveSpectrum { diff --git a/Services/MediaService.qml b/Services/MediaService.qml index c44738d..2ad3da9 100644 --- a/Services/MediaService.qml +++ b/Services/MediaService.qml @@ -12,7 +12,7 @@ Singleton { property var currentPlayer: null property real currentPosition: 0 property int selectedPlayerIndex: 0 - property bool isPlaying: currentPlayer ? currentPlayer.isPlaying : false + property bool isPlaying: currentPlayer ? (currentPlayer.playbackState === MprisPlaybackState.Playing || currentPlayer.isPlaying) : false property string trackTitle: currentPlayer ? (currentPlayer.trackTitle || "") : "" property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "") : "" property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "") : "" @@ -37,11 +37,24 @@ Singleton { let allPlayers = Mpris.players.values let controllablePlayers = [] + // Apply blacklist and controllable filter + const blacklist = (Settings.data.audio && Settings.data.audio.mprisBlacklist) ? Settings.data.audio.mprisBlacklist : [] for (var i = 0; i < allPlayers.length; i++) { let player = allPlayers[i] - if (player && player.canControl) { + if (!player) + continue + const identity = String(player.identity || "") + const busName = String(player.busName || "") + const desktop = String(player.desktopEntry || "") + const idKey = identity.toLowerCase() + const match = blacklist.find(b => { + const s = String(b || "").toLowerCase() + return s && (idKey.includes(s) || busName.toLowerCase().includes(s) || desktop.toLowerCase().includes(s)) + }) + if (match) + continue + if (player.canControl) controllablePlayers.push(player) - } } return controllablePlayers @@ -54,6 +67,22 @@ Singleton { return null } + // Preferred player logic (preferred > fallback) + const preferred = (Settings.data.audio.preferredPlayer || "") + if (preferred !== "") { + for (var i = 0; i < availablePlayers.length; i++) { + const p = availablePlayers[i] + const identity = String(p.identity || "").toLowerCase() + const busName = String(p.busName || "").toLowerCase() + const desktop = String(p.desktopEntry || "").toLowerCase() + const pref = preferred.toLowerCase() + if (identity.includes(pref) || busName.includes(pref) || desktop.includes(pref)) { + selectedPlayerIndex = i + return p + } + } + } + if (selectedPlayerIndex < availablePlayers.length) { return availablePlayers[selectedPlayerIndex] } else {