6 Commits

6 changed files with 264 additions and 122 deletions
+8
View File
@@ -70,6 +70,14 @@ 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="sourceWhitelist" type="Bool">
<label>Only use listed sources</label>
<default>false</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>
+5 -3
View File
@@ -7,13 +7,13 @@ Item {
required property string artworkSource required property string artworkSource
required property var labelLines required property var labelLines
required property string fontFamily required property string fontFamily
required property int fontPixelSize required property real fontPixelSize
required property string textColor required property string textColor
required property bool textShadowEnabled required property bool textShadowEnabled
required property int verticalSpacing required property int verticalSpacing
required property int horizontalAlignment required property int horizontalAlignment
required property int borderRadius required property int borderRadius
required property int artworkSize required property real artworkSize
property string currentArtworkSource: "" property string currentArtworkSource: ""
property string incomingArtworkSource: "" property string incomingArtworkSource: ""
property real currentArtworkOpacity: 0 property real currentArtworkOpacity: 0
@@ -104,7 +104,8 @@ Item {
} }
clip: holdsArtworkSlot clip: holdsArtworkSlot
implicitHeight: holdsArtworkSlot ? artworkSize : labelTextColumn.implicitHeight implicitWidth: artworkSize
implicitHeight: artworkSize
onArtworkSourceChanged: handleArtworkSourceChange() onArtworkSourceChanged: handleArtworkSourceChange()
Component.onCompleted: handleArtworkSourceChange() Component.onCompleted: handleArtworkSourceChange()
@@ -184,6 +185,7 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
spacing: labelContent.verticalSpacing spacing: labelContent.verticalSpacing
opacity: labelContent.textOpacity opacity: labelContent.textOpacity
visible: opacity > 0 visible: opacity > 0
+116 -41
View File
@@ -11,82 +11,159 @@ 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 bool sourceWhitelist: false
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;
}
if (sourceWhitelist && normalizedSourcePriority.length > 0)
return null;
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 +176,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
}
} }
} }
+59 -78
View File
@@ -33,7 +33,23 @@ MouseArea {
readonly property bool hideNowPlayingArea: effectiveLabelVisibilityMode === "never" readonly property bool hideNowPlayingArea: effectiveLabelVisibilityMode === "never"
readonly property bool usesCustomBackground: configuredBackgroundStyle === "custom" readonly property bool usesCustomBackground: configuredBackgroundStyle === "custom"
readonly property int hideModeHorizontalPadding: 8 readonly property int hideModeHorizontalPadding: 8
readonly property int labelRailWidth: 100 readonly property int contentInset: 8
readonly property int availableContentWidth: Math.max(0, width - contentInset * 2)
readonly property int separatorReservedWidth: nowPlayingLabelsVisible && !hideNowPlayingArea ? 1 + effectiveSeparatorGapLabel + effectiveSeparatorGapTrack : 0
readonly property int maxLabelRailSize: Math.max(0, availableContentWidth - separatorReservedWidth)
readonly property int labelRailSize: Math.max(0, Math.min(height - contentInset * 2, maxLabelRailSize))
readonly property bool mediaControlsTargetShown: mediaControlsEnabled && controlsHoverActive && nowPlayingLabelsVisible && !hideNowPlayingArea
readonly property int artworkControlsGap: configuredUseLabelArtwork && player.artUrl.length > 0 ? 2 : 0
readonly property real labelShrunkScale: labelRailSize > 0 ? Math.max(0, (labelRailSize - buttonSize - artworkControlsGap) / labelRailSize) : 1
property real labelContentScale: mediaControlsTargetShown ? labelShrunkScale : 1
readonly property real controlsReveal: {
const range = 1 - labelShrunkScale;
if (range <= 0)
return mediaControlsTargetShown ? 1 : 0;
return Math.max(0, Math.min(1, (1 - labelContentScale) / range));
}
readonly property real labelContentSize: labelRailSize * labelContentScale
readonly property int effectiveImageBorderRadius: Math.max(0, configuredImageBorderRadius) readonly property int effectiveImageBorderRadius: Math.max(0, configuredImageBorderRadius)
readonly property string effectiveBackgroundColor: configuredBackgroundColor || "transparent" readonly property string effectiveBackgroundColor: configuredBackgroundColor || "transparent"
readonly property int effectiveBackgroundRadius: Math.max(0, configuredBackgroundRadius) readonly property int effectiveBackgroundRadius: Math.max(0, configuredBackgroundRadius)
@@ -67,7 +83,7 @@ MouseArea {
onRawHoverActiveChanged: { onRawHoverActiveChanged: {
if (!mediaControlsMouseArea.mediaControlsEnabled) { if (!mediaControlsMouseArea.mediaControlsEnabled) {
hoverExitTimer.stop(); hoverExitTimer.stop();
return ; return;
} }
if (rawHoverActive) if (rawHoverActive)
hoverExitTimer.stop(); hoverExitTimer.stop();
@@ -77,12 +93,11 @@ MouseArea {
onMediaControlsEnabledChanged: { onMediaControlsEnabledChanged: {
if (!mediaControlsMouseArea.mediaControlsEnabled) if (!mediaControlsMouseArea.mediaControlsEnabled)
hoverExitTimer.stop(); hoverExitTimer.stop();
} }
Keys.onPressed: (event) => { Keys.onPressed: event => {
if (!mediaControlsMouseArea.mediaControlsEnabled) { if (!mediaControlsMouseArea.mediaControlsEnabled) {
event.accepted = false; event.accepted = false;
return ; return;
} }
if (!event.modifiers) { if (!event.modifiers) {
event.accepted = true; event.accepted = true;
@@ -110,6 +125,13 @@ MouseArea {
interval: 300 interval: 300
} }
Behavior on labelContentScale {
NumberAnimation {
duration: 250
easing.type: Easing.InOutQuad
}
}
Rectangle { Rectangle {
anchors.fill: parent anchors.fill: parent
visible: mediaControlsMouseArea.usesCustomBackground visible: mediaControlsMouseArea.usesCustomBackground
@@ -119,75 +141,65 @@ MouseArea {
RowLayout { RowLayout {
anchors.fill: parent anchors.fill: parent
anchors.margins: mediaControlsMouseArea.contentInset
layoutDirection: mediaControlsMouseArea.labelsOnRight ? Qt.RightToLeft : Qt.LeftToRight layoutDirection: mediaControlsMouseArea.labelsOnRight ? Qt.RightToLeft : Qt.LeftToRight
ColumnLayout { Item {
id: leftColumn id: leftColumn
visible: !mediaControlsMouseArea.hideNowPlayingArea visible: !mediaControlsMouseArea.hideNowPlayingArea
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumWidth: visible ? mediaControlsMouseArea.labelRailWidth : 0 Layout.minimumWidth: visible ? mediaControlsMouseArea.labelRailSize : 0
Layout.preferredWidth: visible ? mediaControlsMouseArea.labelRailWidth : 0 Layout.preferredWidth: visible ? mediaControlsMouseArea.labelRailSize : 0
Layout.maximumWidth: visible ? mediaControlsMouseArea.labelRailWidth : 0 Layout.maximumWidth: visible ? mediaControlsMouseArea.labelRailSize : 0
states: [
State {
name: "buttonsVisible"
when: mediaControlsMouseArea.controlsHoverActive
PropertyChanges {
target: nowPlayingLabels
Layout.bottomMargin: 10
}
},
State {
name: "buttonsHidden"
when: !mediaControlsMouseArea.controlsHoverActive
PropertyChanges {
target: nowPlayingLabels
Layout.bottomMargin: 0
}
}
]
Item { Item {
Layout.fillHeight: true id: labelSlot
Layout.fillWidth: true
}
ColumnLayout { readonly property bool showsArtwork: mediaControlsMouseArea.configuredUseLabelArtwork && player.artUrl.length > 0
id: nowPlayingLabels // Text is rasterized at whole pixel sizes, then lightly scaled between steps for smoother motion.
readonly property real textTargetFontSize: Math.max(1, mediaControlsMouseArea.effectiveLabelFontSize * mediaControlsMouseArea.labelContentScale)
readonly property int textRasterFontSize: Math.max(1, Math.round(textTargetFontSize))
readonly property real textResidualScale: textTargetFontSize / textRasterFontSize
Layout.alignment: mediaControlsMouseArea.labelsOnRight ? Qt.AlignLeft : Qt.AlignRight x: mediaControlsMouseArea.labelsOnRight ? 0 : leftColumn.width - width
Layout.fillWidth: true y: 0
width: mediaControlsMouseArea.labelContentSize
height: mediaControlsMouseArea.labelContentSize
clip: true
visible: mediaControlsMouseArea.nowPlayingLabelsVisible visible: mediaControlsMouseArea.nowPlayingLabelsVisible
LabelContent { LabelContent {
Layout.alignment: mediaControlsMouseArea.labelsOnRight ? Qt.AlignLeft : Qt.AlignRight anchors.left: parent.left
Layout.fillWidth: true anchors.top: parent.top
width: labelSlot.showsArtwork ? mediaControlsMouseArea.labelRailSize : mediaControlsMouseArea.labelContentSize / labelSlot.textResidualScale
height: labelSlot.showsArtwork ? mediaControlsMouseArea.labelRailSize : mediaControlsMouseArea.labelContentSize / labelSlot.textResidualScale
scale: labelSlot.showsArtwork ? mediaControlsMouseArea.labelContentScale : labelSlot.textResidualScale
transformOrigin: Item.TopLeft
artworkSource: mediaControlsMouseArea.configuredUseLabelArtwork ? player.artUrl : "" artworkSource: mediaControlsMouseArea.configuredUseLabelArtwork ? player.artUrl : ""
labelLines: mediaControlsMouseArea.effectiveLabelLines labelLines: mediaControlsMouseArea.effectiveLabelLines
fontFamily: mediaControlsMouseArea.configuredFontFamily fontFamily: mediaControlsMouseArea.configuredFontFamily
fontPixelSize: mediaControlsMouseArea.effectiveLabelFontSize fontPixelSize: labelSlot.showsArtwork ? mediaControlsMouseArea.effectiveLabelFontSize : labelSlot.textRasterFontSize
textColor: mediaControlsMouseArea.effectiveForegroundColor textColor: mediaControlsMouseArea.effectiveForegroundColor
textShadowEnabled: mediaControlsMouseArea.configuredTextShadowEnabled textShadowEnabled: mediaControlsMouseArea.configuredTextShadowEnabled
verticalSpacing: mediaControlsMouseArea.effectiveLabelVerticalSpacing verticalSpacing: mediaControlsMouseArea.effectiveLabelVerticalSpacing
horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignLeft : Text.AlignRight horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignLeft : Text.AlignRight
borderRadius: mediaControlsMouseArea.effectiveImageBorderRadius borderRadius: mediaControlsMouseArea.effectiveImageBorderRadius
artworkSize: mediaControlsMouseArea.labelRailWidth artworkSize: labelSlot.showsArtwork ? mediaControlsMouseArea.labelRailSize : mediaControlsMouseArea.labelContentSize / labelSlot.textResidualScale
} }
} }
RowLayout { RowLayout {
id: mediaControls id: mediaControls
Layout.alignment: mediaControlsMouseArea.labelsOnRight ? Qt.AlignLeft : Qt.AlignRight x: mediaControlsMouseArea.labelsOnRight ? 0 : leftColumn.width - width
enabled: mediaControlsMouseArea.mediaControlsEnabled && mediaControlsMouseArea.controlsHoverActive y: mediaControlsMouseArea.labelContentSize + mediaControlsMouseArea.artworkControlsGap
opacity: mediaControlsMouseArea.mediaControlsEnabled && mediaControlsMouseArea.controlsHoverActive && mediaControlsMouseArea.nowPlayingLabelsVisible && !mediaControlsMouseArea.hideNowPlayingArea ? 1 : 0 width: implicitWidth
visible: mediaControlsMouseArea.mediaControlsEnabled && mediaControlsMouseArea.controlsHoverActive && mediaControlsMouseArea.nowPlayingLabelsVisible && !mediaControlsMouseArea.hideNowPlayingArea height: mediaControlsMouseArea.buttonSize
clip: true
enabled: mediaControlsMouseArea.mediaControlsTargetShown
opacity: mediaControlsMouseArea.controlsReveal
visible: mediaControlsMouseArea.mediaControlsTargetShown || opacity > 0
QQC2.Button { QQC2.Button {
Layout.preferredWidth: buttonSize Layout.preferredWidth: buttonSize
@@ -203,7 +215,6 @@ MouseArea {
source: "media-skip-backward" source: "media-skip-backward"
color: mediaControlsMouseArea.effectiveForegroundColor color: mediaControlsMouseArea.effectiveForegroundColor
} }
} }
QQC2.Button { QQC2.Button {
@@ -222,7 +233,6 @@ MouseArea {
source: player.playbackStatus === 2 ? "media-playback-pause" : "media-playback-start" source: player.playbackStatus === 2 ? "media-playback-pause" : "media-playback-start"
color: mediaControlsMouseArea.effectiveForegroundColor color: mediaControlsMouseArea.effectiveForegroundColor
} }
} }
QQC2.Button { QQC2.Button {
@@ -236,33 +246,8 @@ MouseArea {
source: "media-skip-forward" source: "media-skip-forward"
color: mediaControlsMouseArea.effectiveForegroundColor color: mediaControlsMouseArea.effectiveForegroundColor
} }
} }
Behavior on opacity {
PropertyAnimation {
duration: 250
easing.type: Easing.InOutQuad
}
}
} }
Item {
Layout.fillHeight: true
Layout.fillWidth: true
}
transitions: Transition {
NumberAnimation {
duration: 250
easing.type: Easing.InOutQuad
properties: "Layout.bottomMargin"
}
}
} }
Item { Item {
@@ -283,7 +268,6 @@ MouseArea {
height: Math.round(parent.height * mediaControlsMouseArea.effectiveSeparatorHeight / 100) height: Math.round(parent.height * mediaControlsMouseArea.effectiveSeparatorHeight / 100)
color: mediaControlsMouseArea.configuredHideSeparator ? "transparent" : mediaControlsMouseArea.effectiveForegroundColor color: mediaControlsMouseArea.configuredHideSeparator ? "transparent" : mediaControlsMouseArea.effectiveForegroundColor
} }
} }
ColumnLayout { ColumnLayout {
@@ -320,9 +304,6 @@ MouseArea {
elide: Text.ElideRight elide: Text.ElideRight
horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignRight : Text.AlignLeft horizontalAlignment: mediaControlsMouseArea.labelsOnRight ? Text.AlignRight : Text.AlignLeft
} }
} }
} }
} }
+69
View File
@@ -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,17 @@ 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_sourceWhitelist: sourceWhitelistCheckBox.checked
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"
@@ -55,6 +60,9 @@ KCM.SimpleKCM {
"text": i18n("Solid background"), "text": i18n("Solid background"),
"value": "default" "value": "default"
}] }]
readonly property bool hasSourcePriorityEntries: sourcePriorityField.text.split(",").some((entry) => {
return normalizeDesktopEntry(entry).length > 0;
})
function syncComboByValue(comboBox, options, configuredValue, fallbackValue) { function syncComboByValue(comboBox, options, configuredValue, fallbackValue) {
const requestedValue = configuredValue || fallbackValue; const requestedValue = configuredValue || fallbackValue;
@@ -71,6 +79,29 @@ 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;
}
Component.onCompleted: cfg_sourceWhitelist = hasSourcePriorityEntries && cfg_sourceWhitelist
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 +222,34 @@ 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("example: spotify, firefox")
onTextChanged: configRoot.cfg_sourceWhitelist = configRoot.hasSourcePriorityEntries && configRoot.cfg_sourceWhitelist
}
QQC2.CheckBox {
id: sourceWhitelistCheckBox
Kirigami.FormData.label: ""
text: i18n("Only use listed sources")
enabled: configRoot.hasSourcePriorityEntries
}
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 +392,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
+7
View File
@@ -43,6 +43,10 @@ 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 bool configuredSourceWhitelist: plasmoid.configuration.sourceWhitelist
// 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 +77,9 @@ PlasmoidItem {
Player { Player {
id: player id: player
sourcePriority: root.configuredSourcePriority
sourceWhitelist: root.configuredSourceWhitelist
} }
fullRepresentation: Representation { fullRepresentation: Representation {