feat(settings): add source priority selection
This commit is contained in:
@@ -70,6 +70,10 @@ PLAYING]]></default>
|
|||||||
<label>Show media controls</label>
|
<label>Show media controls</label>
|
||||||
<default>true</default>
|
<default>true</default>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry name="sourcePriority" type="String">
|
||||||
|
<label>Source priority</label>
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
<entry name="trackTextVerticalSpacing" type="Int">
|
<entry name="trackTextVerticalSpacing" type="Int">
|
||||||
<label>Track text vertical spacing</label>
|
<label>Track text vertical spacing</label>
|
||||||
<default>5</default>
|
<default>5</default>
|
||||||
|
|||||||
+112
-41
@@ -11,82 +11,155 @@ import org.kde.plasma.private.mpris as Mpris
|
|||||||
QtObject {
|
QtObject {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property int containerRole: Qt.UserRole + 1
|
||||||
property string sourceName: "any"
|
property string sourceName: "any"
|
||||||
|
property string sourcePriority: ""
|
||||||
property var mpris2Model
|
property var mpris2Model
|
||||||
|
property int modelRevision: 0
|
||||||
|
readonly property var normalizedSourcePriority: parseSourcePriority(sourcePriority)
|
||||||
|
readonly property var resolvedPlayer: resolvePlayer()
|
||||||
readonly property bool ready: {
|
readonly property bool ready: {
|
||||||
if (!mpris2Model.currentPlayer)
|
if (!resolvedPlayer)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return mpris2Model.currentPlayer.desktopEntry === sourceName || sourceName === "any";
|
return sourceName === "any" || entriesMatch(resolvedPlayer.desktopEntry, sourceName);
|
||||||
}
|
}
|
||||||
readonly property string artists: ready ? mpris2Model.currentPlayer.artist : ""
|
readonly property string artists: ready ? resolvedPlayer.artist : ""
|
||||||
readonly property string title: ready ? mpris2Model.currentPlayer.track : ""
|
readonly property string title: ready ? resolvedPlayer.track : ""
|
||||||
readonly property int playbackStatus: ready ? mpris2Model.currentPlayer.playbackStatus : Mpris.PlaybackStatus.Unknown
|
readonly property int playbackStatus: ready ? resolvedPlayer.playbackStatus : Mpris.PlaybackStatus.Unknown
|
||||||
readonly property int shuffle: ready ? mpris2Model.currentPlayer.shuffle : Mpris.ShuffleStatus.Unknown
|
readonly property int shuffle: ready ? resolvedPlayer.shuffle : Mpris.ShuffleStatus.Unknown
|
||||||
readonly property string artUrl: ready ? mpris2Model.currentPlayer.artUrl : ""
|
readonly property string artUrl: ready ? resolvedPlayer.artUrl : ""
|
||||||
readonly property int loopStatus: ready ? mpris2Model.currentPlayer.loopStatus : Mpris.LoopStatus.Unknown
|
readonly property int loopStatus: ready ? resolvedPlayer.loopStatus : Mpris.LoopStatus.Unknown
|
||||||
readonly property double songPosition: ready ? mpris2Model.currentPlayer.position : 0
|
readonly property double songPosition: ready ? resolvedPlayer.position : 0
|
||||||
readonly property double songLength: ready ? mpris2Model.currentPlayer.length : 0
|
readonly property double songLength: ready ? resolvedPlayer.length : 0
|
||||||
readonly property real volume: ready ? mpris2Model.currentPlayer.volume : 0
|
readonly property real volume: ready ? resolvedPlayer.volume : 0
|
||||||
readonly property string identity: ready ? mpris2Model.currentPlayer.identity : ""
|
readonly property string identity: ready ? resolvedPlayer.identity : ""
|
||||||
readonly property bool canGoNext: ready ? mpris2Model.currentPlayer.canGoNext : false
|
readonly property bool canGoNext: ready ? resolvedPlayer.canGoNext : false
|
||||||
readonly property bool canGoPrevious: ready ? mpris2Model.currentPlayer.canGoPrevious : false
|
readonly property bool canGoPrevious: ready ? resolvedPlayer.canGoPrevious : false
|
||||||
readonly property bool canPlay: ready ? mpris2Model.currentPlayer.canPlay : false
|
readonly property bool canPlay: ready ? resolvedPlayer.canPlay : false
|
||||||
readonly property bool canPause: ready ? mpris2Model.currentPlayer.canPause : false
|
readonly property bool canPause: ready ? resolvedPlayer.canPause : false
|
||||||
readonly property bool canSeek: ready ? mpris2Model.currentPlayer.canSeek : false
|
readonly property bool canSeek: ready ? resolvedPlayer.canSeek : false
|
||||||
readonly property bool canRaise: ready ? mpris2Model.currentPlayer.canRaise : false
|
readonly property bool canRaise: ready ? resolvedPlayer.canRaise : false
|
||||||
// To know whether Shuffle and Loop can be changed we have to check if the property is defined,
|
// To know whether Shuffle and Loop can be changed we have to check if the property is defined,
|
||||||
// unlike the other commands, LoopStatus and Shuffle do not have specific properties such as
|
// unlike the other commands, LoopStatus and Shuffle do not have specific properties such as
|
||||||
// CanPause, CanSeek, etc.
|
// CanPause, CanSeek, etc.
|
||||||
readonly property bool canChangeShuffle: ready ? mpris2Model.currentPlayer.shuffle !== undefined : false
|
readonly property bool canChangeShuffle: ready ? resolvedPlayer.shuffle !== undefined : false
|
||||||
readonly property bool canChangeLoopStatus: ready ? mpris2Model.currentPlayer.loopStatus !== undefined : false
|
readonly property bool canChangeLoopStatus: ready ? resolvedPlayer.loopStatus !== undefined : false
|
||||||
|
|
||||||
|
function normalizedEntry(value) {
|
||||||
|
return (value || "").toString().trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function entriesMatch(left, right) {
|
||||||
|
const normalizedLeft = normalizedEntry(left);
|
||||||
|
const normalizedRight = normalizedEntry(right);
|
||||||
|
if (!normalizedLeft || !normalizedRight)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return normalizedLeft === normalizedRight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSourcePriority(value) {
|
||||||
|
return (value || "").split(",").map((entry) => {
|
||||||
|
return normalizedEntry(entry);
|
||||||
|
}).filter((entry) => {
|
||||||
|
return entry.length > 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function playerAt(row) {
|
||||||
|
if (!mpris2Model || row < 0 || row >= mpris2Model.rowCount())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return mpris2Model.data(mpris2Model.index(row, 0), containerRole);
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPlayerByEntry(targetEntry) {
|
||||||
|
if (!targetEntry || !mpris2Model)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const playerCount = mpris2Model.rowCount();
|
||||||
|
for (let row = 0; row < playerCount; ++row) {
|
||||||
|
const player = playerAt(row);
|
||||||
|
if (player && entriesMatch(player.desktopEntry, targetEntry))
|
||||||
|
return player;
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolvePlayer() {
|
||||||
|
modelRevision;
|
||||||
|
if (!mpris2Model)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
for (const targetEntry of normalizedSourcePriority) {
|
||||||
|
const matchedPlayer = findPlayerByEntry(targetEntry);
|
||||||
|
if (matchedPlayer)
|
||||||
|
return matchedPlayer;
|
||||||
|
|
||||||
|
}
|
||||||
|
return mpris2Model.currentPlayer || null;
|
||||||
|
}
|
||||||
|
|
||||||
function togglePlayPause() {
|
function togglePlayPause() {
|
||||||
if (mpris2Model.currentPlayer)
|
if (resolvedPlayer)
|
||||||
mpris2Model.currentPlayer.PlayPause();
|
resolvedPlayer.PlayPause();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setPosition(position) {
|
function setPosition(position) {
|
||||||
mpris2Model.currentPlayer.position = position;
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.position = position;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function next() {
|
function next() {
|
||||||
if (mpris2Model.currentPlayer)
|
if (resolvedPlayer)
|
||||||
mpris2Model.currentPlayer.Next();
|
resolvedPlayer.Next();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function previous() {
|
function previous() {
|
||||||
if (mpris2Model.currentPlayer)
|
if (resolvedPlayer)
|
||||||
mpris2Model.currentPlayer.Previous();
|
resolvedPlayer.Previous();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePosition() {
|
function updatePosition() {
|
||||||
if (mpris2Model.currentPlayer)
|
if (resolvedPlayer)
|
||||||
mpris2Model.currentPlayer.updatePosition();
|
resolvedPlayer.updatePosition();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setVolume(volume) {
|
function setVolume(volume) {
|
||||||
mpris2Model.currentPlayer.volume = volume;
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.volume = volume;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeVolume(delta, showOSD) {
|
function changeVolume(delta, showOSD) {
|
||||||
mpris2Model.currentPlayer.changeVolume(delta, showOSD);
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.changeVolume(delta, showOSD);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setShuffle(shuffle) {
|
function setShuffle(shuffle) {
|
||||||
mpris2Model.currentPlayer.shuffle = shuffle;
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.shuffle = shuffle;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLoopStatus(loopStatus) {
|
function setLoopStatus(loopStatus) {
|
||||||
mpris2Model.currentPlayer.loopStatus = loopStatus;
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.loopStatus = loopStatus;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function raise() {
|
function raise() {
|
||||||
mpris2Model.currentPlayer.Raise();
|
if (resolvedPlayer)
|
||||||
|
resolvedPlayer.Raise();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTrackTime(s) {
|
function formatTrackTime(s) {
|
||||||
@@ -99,13 +172,11 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mpris2Model: Mpris.Mpris2Model {
|
mpris2Model: Mpris.Mpris2Model {
|
||||||
onRowsInserted: (_, rowIndex) => {
|
onRowsInserted: root.modelRevision += 1
|
||||||
const CONTAINER_ROLE = Qt.UserRole + 1;
|
onRowsRemoved: root.modelRevision += 1
|
||||||
const player = this.data(this.index(rowIndex, 0), CONTAINER_ROLE);
|
onModelReset: root.modelRevision += 1
|
||||||
if (player.desktopEntry === root.sourceName)
|
onDataChanged: root.modelRevision += 1
|
||||||
this.currentIndex = rowIndex;
|
onCurrentPlayerChanged: root.modelRevision += 1
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import QtQuick.Dialogs as Dialogs
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import org.kde.kcmutils as KCM
|
import org.kde.kcmutils as KCM
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.plasma.private.mpris as Mpris
|
||||||
|
|
||||||
KCM.SimpleKCM {
|
KCM.SimpleKCM {
|
||||||
id: configRoot
|
id: configRoot
|
||||||
@@ -24,13 +25,16 @@ KCM.SimpleKCM {
|
|||||||
property alias cfg_foregroundColor: foregroundColorField.text
|
property alias cfg_foregroundColor: foregroundColorField.text
|
||||||
property alias cfg_textShadowEnabled: textShadowCheckBox.checked
|
property alias cfg_textShadowEnabled: textShadowCheckBox.checked
|
||||||
property alias cfg_showMediaControls: showMediaControlsCheckBox.checked
|
property alias cfg_showMediaControls: showMediaControlsCheckBox.checked
|
||||||
|
property alias cfg_sourcePriority: sourcePriorityField.text
|
||||||
property alias cfg_trackTextVerticalSpacing: trackTextVerticalSpacingSpinBox.value
|
property alias cfg_trackTextVerticalSpacing: trackTextVerticalSpacingSpinBox.value
|
||||||
property alias cfg_labelVerticalSpacing: labelVerticalSpacingSpinBox.value
|
property alias cfg_labelVerticalSpacing: labelVerticalSpacingSpinBox.value
|
||||||
property alias cfg_separatorGapLabel: separatorGapLabelSpinBox.value
|
property alias cfg_separatorGapLabel: separatorGapLabelSpinBox.value
|
||||||
property alias cfg_separatorGapTrack: separatorGapTrackSpinBox.value
|
property alias cfg_separatorGapTrack: separatorGapTrackSpinBox.value
|
||||||
property alias cfg_separatorHeight: separatorHeightSpinBox.value
|
property alias cfg_separatorHeight: separatorHeightSpinBox.value
|
||||||
property alias cfg_hideSeparator: hideSeparatorCheckBox.checked
|
property alias cfg_hideSeparator: hideSeparatorCheckBox.checked
|
||||||
|
readonly property int containerRole: Qt.UserRole + 1
|
||||||
readonly property var availableFonts: Qt.fontFamilies()
|
readonly property var availableFonts: Qt.fontFamilies()
|
||||||
|
property var detectedSources: []
|
||||||
readonly property var labelVisibilityOptions: [{
|
readonly property var labelVisibilityOptions: [{
|
||||||
"text": i18n("Auto hide when idle"),
|
"text": i18n("Auto hide when idle"),
|
||||||
"value": "auto"
|
"value": "auto"
|
||||||
@@ -71,6 +75,28 @@ KCM.SimpleKCM {
|
|||||||
return options[resolvedIndex].value;
|
return options[resolvedIndex].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeDesktopEntry(value) {
|
||||||
|
return (value || "").toString().trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshDetectedSources() {
|
||||||
|
const sources = [];
|
||||||
|
const seen = {
|
||||||
|
};
|
||||||
|
const playerCount = detectedSourcesModel.rowCount();
|
||||||
|
for (let row = 0; row < playerCount; ++row) {
|
||||||
|
const player = detectedSourcesModel.data(detectedSourcesModel.index(row, 0), containerRole);
|
||||||
|
const desktopEntry = normalizeDesktopEntry(player ? player.desktopEntry : "");
|
||||||
|
const desktopEntryKey = desktopEntry.toLowerCase();
|
||||||
|
if (!desktopEntry || seen[desktopEntryKey])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
seen[desktopEntryKey] = true;
|
||||||
|
sources.push(desktopEntry);
|
||||||
|
}
|
||||||
|
detectedSources = sources;
|
||||||
|
}
|
||||||
|
|
||||||
onCfg_fontFamilyChanged: {
|
onCfg_fontFamilyChanged: {
|
||||||
const index = availableFonts.indexOf(cfg_fontFamily);
|
const index = availableFonts.indexOf(cfg_fontFamily);
|
||||||
if (index >= 0 && fontFamilyComboBox.currentIndex !== index)
|
if (index >= 0 && fontFamilyComboBox.currentIndex !== index)
|
||||||
@@ -191,6 +217,25 @@ KCM.SimpleKCM {
|
|||||||
text: i18n("Show media controls")
|
text: i18n("Show media controls")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QQC2.TextField {
|
||||||
|
id: sourcePriorityField
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Source priority:")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
placeholderText: i18n("spotify, firefox")
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
id: detectedSourcesField
|
||||||
|
|
||||||
|
Kirigami.FormData.label: i18n("Current sources being detected:")
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Math.max(implicitHeight, Kirigami.Units.gridUnit * 2)
|
||||||
|
textFormat: TextEdit.PlainText
|
||||||
|
wrapMode: TextEdit.WrapAnywhere
|
||||||
|
text: configRoot.detectedSources.length > 0 ? configRoot.detectedSources.join("\n") : i18n("No active MPRIS desktopEntry values detected")
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.SpinBox {
|
QQC2.SpinBox {
|
||||||
id: separatorHeightSpinBox
|
id: separatorHeightSpinBox
|
||||||
|
|
||||||
@@ -333,6 +378,16 @@ KCM.SimpleKCM {
|
|||||||
onAccepted: configRoot.cfg_foregroundColor = selectedColor.toString()
|
onAccepted: configRoot.cfg_foregroundColor = selectedColor.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Mpris.Mpris2Model {
|
||||||
|
id: detectedSourcesModel
|
||||||
|
|
||||||
|
onRowsInserted: configRoot.refreshDetectedSources()
|
||||||
|
onRowsRemoved: configRoot.refreshDetectedSources()
|
||||||
|
onModelReset: configRoot.refreshDetectedSources()
|
||||||
|
onDataChanged: configRoot.refreshDetectedSources()
|
||||||
|
Component.onCompleted: configRoot.refreshDetectedSources()
|
||||||
|
}
|
||||||
|
|
||||||
component SectionHeader: Item {
|
component SectionHeader: Item {
|
||||||
required property string title
|
required property string title
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ PlasmoidItem {
|
|||||||
// qmllint disable missing-property
|
// qmllint disable missing-property
|
||||||
property bool configuredShowMediaControls: plasmoid.configuration.showMediaControls
|
property bool configuredShowMediaControls: plasmoid.configuration.showMediaControls
|
||||||
// qmllint disable missing-property
|
// qmllint disable missing-property
|
||||||
|
property string configuredSourcePriority: plasmoid.configuration.sourcePriority
|
||||||
|
// qmllint disable missing-property
|
||||||
property int configuredTrackTextVerticalSpacing: plasmoid.configuration.trackTextVerticalSpacing
|
property int configuredTrackTextVerticalSpacing: plasmoid.configuration.trackTextVerticalSpacing
|
||||||
// qmllint disable missing-property
|
// qmllint disable missing-property
|
||||||
property int configuredLabelVerticalSpacing: plasmoid.configuration.labelVerticalSpacing
|
property int configuredLabelVerticalSpacing: plasmoid.configuration.labelVerticalSpacing
|
||||||
@@ -73,6 +75,8 @@ PlasmoidItem {
|
|||||||
|
|
||||||
Player {
|
Player {
|
||||||
id: player
|
id: player
|
||||||
|
|
||||||
|
sourcePriority: root.configuredSourcePriority
|
||||||
}
|
}
|
||||||
|
|
||||||
fullRepresentation: Representation {
|
fullRepresentation: Representation {
|
||||||
|
|||||||
Reference in New Issue
Block a user