mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-04-29 15:03:09 +00:00
widgets/mediacontrols: Port to kmpris
This commit is contained in:
parent
ee803b400a
commit
ae8a278ab6
2 changed files with 73 additions and 121 deletions
|
|
@ -1,111 +1,34 @@
|
||||||
/*
|
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
// SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
* SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
import org.kde.plasma.plasma5support 2.0 as P5Support
|
import org.kde.plasma.private.mpris as Mpris
|
||||||
|
|
||||||
P5Support.DataSource {
|
QtObject {
|
||||||
id: mpris2Source
|
property var mpris2Model: Mpris.Mpris2Model {}
|
||||||
|
|
||||||
engine: "mpris2"
|
|
||||||
connectedSources: sources
|
|
||||||
|
|
||||||
readonly property string multiplexSource: "@multiplex"
|
readonly property string multiplexSource: "@multiplex"
|
||||||
|
|
||||||
property var mprisSourcesModel: []
|
property var mprisSourcesModel: []
|
||||||
|
|
||||||
readonly property bool hasPlayer: sources.length > 1
|
|
||||||
|
|
||||||
function startOperation(src, op) {
|
function startOperation(src, op) {
|
||||||
var service = serviceForSource(src)
|
var service = serviceForSource(src)
|
||||||
var operation = service.operationDescription(op)
|
var operation = service.operationDescription(op)
|
||||||
return service.startOperationCall(operation)
|
return service.startOperationCall(operation)
|
||||||
}
|
}
|
||||||
|
|
||||||
function goPrevious(source) {
|
function setIndex(index) {
|
||||||
startOperation(source, "Previous");
|
mpris2Model.currentIndex = index;
|
||||||
}
|
}
|
||||||
function goNext(source) {
|
function goPrevious() {
|
||||||
startOperation(source, "Next");
|
mpris2Model.currentPlayer.Previous();
|
||||||
}
|
}
|
||||||
function playPause(source) {
|
function goNext() {
|
||||||
startOperation(source, "PlayPause");
|
mpris2Model.currentPlayer.Next();
|
||||||
}
|
}
|
||||||
function isPlaying(source) {
|
function playPause() {
|
||||||
return data[source] ? data[source].PlaybackStatus === "Playing" : false;
|
mpris2Model.currentPlayer.PlayPause();
|
||||||
}
|
}
|
||||||
function canControl(source) {
|
|
||||||
return data[source] ? data[source].CanControl : false;
|
|
||||||
}
|
|
||||||
function canGoBack(source) {
|
|
||||||
return data[source] ? data[source].CanGoPrevious : false;
|
|
||||||
}
|
|
||||||
function canGoNext(source) {
|
|
||||||
return data[source] ? data[source].CanGoNext : false;
|
|
||||||
}
|
|
||||||
function track(source) {
|
|
||||||
if (!data[source]) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
const xesamTitle = data[source].Metadata["xesam:title"]
|
|
||||||
if (xesamTitle) {
|
|
||||||
return xesamTitle
|
|
||||||
}
|
|
||||||
// if no track title is given, print out the file name
|
|
||||||
const xesamUrl = data[source].Metadata["xesam:url"] ? data[source].Metadata["xesam:url"].toString() : ""
|
|
||||||
if (!xesamUrl) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
const lastSlashPos = xesamUrl.lastIndexOf('/')
|
|
||||||
if (lastSlashPos < 0) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
const lastUrlPart = xesamUrl.substring(lastSlashPos + 1)
|
|
||||||
return decodeURIComponent(lastUrlPart)
|
|
||||||
}
|
|
||||||
function artist(source) {
|
|
||||||
return data[source] ? data[source].Metadata["xesam:artist"] || "" : "";
|
|
||||||
}
|
|
||||||
function albumArt(source) {
|
|
||||||
return data[source] ? data[source].Metadata["mpris:artUrl"] || "" : "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMprisSourcesModel() {
|
|
||||||
let model = [];
|
|
||||||
|
|
||||||
let sources = mpris2Source.sources;
|
|
||||||
for (let i = 0; i < sources.length; ++i) {
|
|
||||||
let source = sources[i];
|
|
||||||
if (source === mpris2Source.multiplexSource) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const playerData = mpris2Source.data[source];
|
|
||||||
// source data is removed before its name is removed from the list
|
|
||||||
if (!playerData) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
model.push({
|
|
||||||
'application': playerData["Identity"],
|
|
||||||
'source': source,
|
|
||||||
'desktopEntry': playerData["DesktopEntry"]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
mprisSourcesModel = model;
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
mpris2Source.serviceForSource("@multiplex").enableGlobalShortcuts()
|
|
||||||
updateMprisSourcesModel()
|
|
||||||
}
|
|
||||||
|
|
||||||
onSourceAdded: updateMprisSourcesModel()
|
|
||||||
onSourceRemoved: updateMprisSourcesModel();
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
/*
|
// SPDX-FileCopyrightText: 2021-2023 Devin Lin <devin@kde.org>
|
||||||
* SPDX-FileCopyrightText: 2021 Devin Lin <devin@kde.org>
|
// SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
||||||
* SPDX-FileCopyrightText: 2016 Kai Uwe Broulik <kde@privat.broulik.de>
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
|
@ -14,6 +11,8 @@ import org.kde.kirigami as Kirigami
|
||||||
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
import org.kde.plasma.private.mobileshell.state 1.0 as MobileShellState
|
||||||
import org.kde.plasma.components 3.0 as PlasmaComponents3
|
import org.kde.plasma.components 3.0 as PlasmaComponents3
|
||||||
|
|
||||||
|
import org.kde.plasma.private.mpris as Mpris
|
||||||
|
|
||||||
import "../../components" as Components
|
import "../../components" as Components
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -21,10 +20,10 @@ import "../../components" as Components
|
||||||
*/
|
*/
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
visible: mpris2Source.hasPlayer
|
visible: sourceRepeater.count > 0
|
||||||
|
|
||||||
readonly property real padding: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing
|
readonly property real padding: Kirigami.Units.gridUnit
|
||||||
readonly property real contentHeight: Kirigami.Units.gridUnit * 2 + Kirigami.Units.smallSpacing
|
readonly property real contentHeight: Kirigami.Units.gridUnit * 2
|
||||||
implicitHeight: visible ? padding * 2 + contentHeight : 0
|
implicitHeight: visible ? padding * 2 + contentHeight : 0
|
||||||
|
|
||||||
MediaControlsSource {
|
MediaControlsSource {
|
||||||
|
|
@ -36,7 +35,7 @@ Item {
|
||||||
z: 1
|
z: 1
|
||||||
visible: view.count > 1
|
visible: view.count > 1
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
anchors.bottomMargin: Kirigami.Units.smallSpacing * 2
|
anchors.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
anchors.bottom: view.bottom
|
anchors.bottom: view.bottom
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
|
||||||
|
|
@ -59,20 +58,39 @@ Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: mpris2Source.mprisSourcesModel
|
id: sourceRepeater
|
||||||
|
model: mpris2Source.mpris2Model
|
||||||
|
|
||||||
delegate: Loader {
|
delegate: Loader {
|
||||||
active: modelData
|
id: delegate
|
||||||
|
// NOTE: model is PlayerContainer from KMpris in plasma-workspace
|
||||||
|
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
|
|
||||||
|
function getTrackName() {
|
||||||
|
console.log('track name: ' + model.title);
|
||||||
|
if (model.title) {
|
||||||
|
return model.title;
|
||||||
|
}
|
||||||
|
// if no track title is given, print out the file name
|
||||||
|
if (!model.url) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const lastSlashPos = model.url.lastIndexOf('/')
|
||||||
|
if (lastSlashPos < 0) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
const lastUrlPart = model.url.substring(lastSlashPos + 1);
|
||||||
|
return decodeURIComponent(lastUrlPart);
|
||||||
|
}
|
||||||
|
|
||||||
sourceComponent: MouseArea {
|
sourceComponent: MouseArea {
|
||||||
id: mouseArea
|
id: mouseArea
|
||||||
implicitHeight: playerItem.implicitHeight
|
implicitHeight: playerItem.implicitHeight
|
||||||
implicitWidth: playerItem.implicitWidth
|
implicitWidth: playerItem.implicitWidth
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Components.AppLaunch.launchOrActivateApp(modelData.desktopEntry + ".desktop");
|
Components.AppLaunch.launchOrActivateApp(model.desktopEntry + ".desktop");
|
||||||
MobileShellState.ShellDBusClient.closeActionDrawer();
|
MobileShellState.ShellDBusClient.closeActionDrawer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -80,15 +98,13 @@ Item {
|
||||||
id: playerItem
|
id: playerItem
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
property string source: modelData.source
|
|
||||||
|
|
||||||
padding: root.padding
|
padding: root.padding
|
||||||
implicitHeight: root.contentHeight + root.padding * 2
|
implicitHeight: root.contentHeight + root.padding * 2
|
||||||
implicitWidth: root.width
|
implicitWidth: root.width
|
||||||
|
|
||||||
background: BlurredBackground {
|
background: BlurredBackground {
|
||||||
darken: mouseArea.pressed
|
darken: mouseArea.pressed
|
||||||
imageSource: mpris2Source.albumArt(playerItem.source)
|
imageSource: model.artUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
|
|
@ -102,7 +118,7 @@ Item {
|
||||||
height: parent.height
|
height: parent.height
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
enabled: mpris2Source.canControl(playerItem.source)
|
enabled: model.canControl
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
id: albumArt
|
id: albumArt
|
||||||
|
|
@ -110,7 +126,7 @@ Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
asynchronous: true
|
asynchronous: true
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
source: mpris2Source.albumArt(playerItem.source)
|
source: model.artUrl
|
||||||
sourceSize.height: height
|
sourceSize.height: height
|
||||||
visible: status === Image.Loading || status === Image.Ready
|
visible: status === Image.Loading || status === Image.Ready
|
||||||
}
|
}
|
||||||
|
|
@ -120,20 +136,24 @@ Item {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
// media track name text
|
||||||
Components.MarqueeLabel {
|
Components.MarqueeLabel {
|
||||||
|
id: trackLabel
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
inputText: mpris2Source.track(playerItem.source) || i18n("No media playing")
|
inputText: model.track || i18n("No media playing");
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// media artist name text
|
||||||
Components.MarqueeLabel {
|
Components.MarqueeLabel {
|
||||||
|
id: artistLabel
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
// if no artist is given, show player name instead
|
// if no artist is given, show player name instead
|
||||||
inputText: mpris2Source.artist(playerItem.source) || modelData.application || ""
|
inputText: model.artist || model.identity || ""
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 0.9
|
||||||
opacity: 0.9
|
opacity: 0.9
|
||||||
|
|
@ -145,12 +165,15 @@ Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
|
|
||||||
enabled: mpris2Source.canGoBack(playerItem.source)
|
enabled: model.canGoPrevious
|
||||||
icon.name: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward"
|
icon.name: LayoutMirroring.enabled ? "media-skip-forward" : "media-skip-backward"
|
||||||
icon.width: Kirigami.Units.iconSizes.small
|
icon.width: Kirigami.Units.iconSizes.small
|
||||||
icon.height: Kirigami.Units.iconSizes.small
|
icon.height: Kirigami.Units.iconSizes.small
|
||||||
onClicked: mpris2Source.goPrevious(playerItem.source)
|
onClicked: {
|
||||||
visible: mpris2Source.canGoBack(playerItem.source) || mpris2Source.canGoNext(playerItem.source)
|
mpris2Source.setIndex(model.index);
|
||||||
|
mpris2Source.goPrevious();
|
||||||
|
}
|
||||||
|
visible: model.canGoPrevious || model.canGoNext
|
||||||
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Previous track")
|
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Previous track")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,10 +181,13 @@ Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
|
|
||||||
icon.name: mpris2Source.isPlaying(playerItem.source) ? "media-playback-pause" : "media-playback-start"
|
icon.name: (model.playbackStatus === Mpris.PlaybackStatus.Playing) ? "media-playback-pause" : "media-playback-start"
|
||||||
icon.width: Kirigami.Units.iconSizes.small
|
icon.width: Kirigami.Units.iconSizes.small
|
||||||
icon.height: Kirigami.Units.iconSizes.small
|
icon.height: Kirigami.Units.iconSizes.small
|
||||||
onClicked: mpris2Source.playPause(playerItem.source)
|
onClicked: {
|
||||||
|
mpris2Source.setIndex(model.index);
|
||||||
|
mpris2Source.playPause();
|
||||||
|
}
|
||||||
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Play or Pause media")
|
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Play or Pause media")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,12 +195,15 @@ Item {
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
|
|
||||||
enabled: mpris2Source.canGoBack(playerItem.source)
|
enabled: model.canGoNext
|
||||||
icon.name: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward"
|
icon.name: LayoutMirroring.enabled ? "media-skip-backward" : "media-skip-forward"
|
||||||
icon.width: Kirigami.Units.iconSizes.small
|
icon.width: Kirigami.Units.iconSizes.small
|
||||||
icon.height: Kirigami.Units.iconSizes.small
|
icon.height: Kirigami.Units.iconSizes.small
|
||||||
onClicked: mpris2Source.goNext(playerItem.source)
|
onClicked: {
|
||||||
visible: mpris2Source.canGoBack(playerItem.source) || mpris2Source.canGoNext(playerItem.source)
|
mpris2Source.setIndex(model.index);
|
||||||
|
mpris2Source.goNext();
|
||||||
|
}
|
||||||
|
visible: model.canGoPrevious || model.canGoNext
|
||||||
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Next track")
|
Accessible.name: i18nd("plasma_lookandfeel_org.kde.lookandfeel", "Next track")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue