main.qml 11.8 KB
Newer Older
1
2
/***************************************************************************
 *   Copyright 2013 Sebastian Kügler <sebas@kde.org>                       *
Kai Uwe Broulik's avatar
Kai Uwe Broulik committed
3
 *   Copyright 2014 Kai Uwe Broulik <kde@privat.broulik.de>                *
4
 *   Copyright 2020 Ismael Asensio <isma.af@gmail.com>                     *
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU Library General Public License as       *
 *   published by the Free Software Foundation; either version 2 of the    *
 *   License, or (at your option) any later version.                       *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this program; if not, write to the                 *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .        *
 ***************************************************************************/

import QtQuick 2.0
import QtQuick.Layouts 1.1
import org.kde.plasma.plasmoid 2.0
import org.kde.plasma.core 2.0 as PlasmaCore
import org.kde.plasma.components 2.0 as PlasmaComponents
import org.kde.plasma.extras 2.0 as PlasmaExtras

Item {
    id: root

32
    property var currentMetadata: mpris2Source.currentData ? mpris2Source.currentData.Metadata : undefined
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
    property string track: {
        if (!currentMetadata) {
            return ""
        }
        var xesamTitle = currentMetadata["xesam:title"]
        if (xesamTitle) {
            return xesamTitle
        }
        // if no track title is given, print out the file name
        var xesamUrl = currentMetadata["xesam:url"] ? currentMetadata["xesam:url"].toString() : ""
        if (!xesamUrl) {
            return ""
        }
        var lastSlashPos = xesamUrl.lastIndexOf('/')
        if (lastSlashPos < 0) {
            return ""
        }
        var lastUrlPart = xesamUrl.substring(lastSlashPos + 1)
51
        return decodeURIComponent(lastUrlPart)
52
    }
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
    property string artist: {
        if (!currentMetadata) {
            return ""
        }
        var xesamArtist = currentMetadata["xesam:artist"]
        if (!xesamArtist) {
            return "";
        }

        if (typeof xesamArtist == "string") {
            return xesamArtist
        } else {
            return xesamArtist.join(", ")
        }
    }
68
    property string albumArt: currentMetadata ? currentMetadata["mpris:artUrl"] || "" : ""
69

70
71
    readonly property string identity: !root.noPlayer ? mpris2Source.currentData.Identity || mpris2Source.current : ""

72
    property bool noPlayer: mpris2Source.sources.length <= 1
73

74
75
    property var mprisSourcesModel: []

76
77
78
79
80
    readonly property bool canControl: (!root.noPlayer && mpris2Source.currentData.CanControl) || false
    readonly property bool canGoPrevious: (canControl && mpris2Source.currentData.CanGoPrevious) || false
    readonly property bool canGoNext: (canControl && mpris2Source.currentData.CanGoNext) || false
    readonly property bool canPlay: (canControl && mpris2Source.currentData.CanPlay) || false
    readonly property bool canPause: (canControl && mpris2Source.currentData.CanPause) || false
81

82
83
    Plasmoid.switchWidth: units.gridUnit * 14
    Plasmoid.switchHeight: units.gridUnit * 10
84
    Plasmoid.icon: "media-playback-playing"
85
    Plasmoid.toolTipMainText: i18n("No media playing")
86
    Plasmoid.toolTipSubText: identity
87
    Plasmoid.toolTipTextFormat: Text.PlainText
88
    Plasmoid.status: PlasmaCore.Types.PassiveStatus
89

90
91
    Plasmoid.onContextualActionsAboutToShow: {
        plasmoid.clearActions()
92
93
94
95
96
97

        if (root.noPlayer) {
            return
        }

        if (mpris2Source.currentData.CanRaise) {
98
99
            var icon = mpris2Source.currentData["Desktop Icon Name"] || ""
            plasmoid.setAction("open", i18nc("Open player window or bring it to the front if already open", "Open"), icon)
100
101
102
        }

        if (canControl) {
103
104
            plasmoid.setAction("previous", i18nc("Play previous track", "Previous Track"),
                               Qt.application.layoutDirection === Qt.RightToLeft ? "media-skip-forward" : "media-skip-backward");
105
106
107
108
            plasmoid.action("previous").enabled = Qt.binding(function() {
                return root.canGoPrevious
            })

109
110
111
112
113
            // if CanPause, toggle the menu entry between Play & Pause, otherwise always use Play
            if (root.state == "playing" && root.canPause) {
                plasmoid.setAction("pause", i18nc("Pause playback", "Pause"), "media-playback-pause")
                plasmoid.action("pause").enabled = Qt.binding(function() {
                    return root.state === "playing" && root.canPause;
114
                });
115
            } else {
116
117
118
                plasmoid.setAction("play", i18nc("Start playback", "Play"), "media-playback-start")
                plasmoid.action("play").enabled = Qt.binding(function() {
                    return root.state !== "playing" && root.canPlay;
119
                });
120
121
            }

122
123
            plasmoid.setAction("next", i18nc("Play next track", "Next Track"),
                               Qt.application.layoutDirection === Qt.RightToLeft ? "media-skip-backward" : "media-skip-forward")
124
125
126
127
128
            plasmoid.action("next").enabled = Qt.binding(function() {
                return root.canGoNext
            })

            plasmoid.setAction("stop", i18nc("Stop playback", "Stop"), "media-playback-stop")
129
130
131
            plasmoid.action("stop").enabled = Qt.binding(function() {
                return root.state === "playing" || root.state === "paused";
            })
132
        }
133

134
        if (mpris2Source.currentData.CanQuit) {
135
136
            plasmoid.setActionSeparator("quitseparator");
            plasmoid.setAction("quit", i18nc("Quit player", "Quit"), "application-exit")
137
        }
138
139
    }

140
141
    // HACK Some players like Amarok take quite a while to load the next track
    // this avoids having the plasmoid jump between popup and panel
142
143
144
145
146
147
148
    onStateChanged: {
        if (state != "") {
            plasmoid.status = PlasmaCore.Types.ActiveStatus
        } else {
            updatePlasmoidStatusTimer.restart()
        }
    }
149
150
151

    Timer {
        id: updatePlasmoidStatusTimer
152
        interval: 3000
153
154
155
156
157
158
159
        onTriggered: {
            if (state != "") {
                plasmoid.status = PlasmaCore.Types.ActiveStatus
            } else {
                plasmoid.status = PlasmaCore.Types.PassiveStatus
            }
        }
160
161
    }

162
163
164
    Plasmoid.fullRepresentation: ExpandedRepresentation {}

    Plasmoid.compactRepresentation: PlasmaCore.IconItem {
165
166
167
        source: root.state === "playing" ? "media-playback-playing" :
                root.state === "paused" ?  "media-playback-paused" :
                                           "media-playback-stopped"
168
169
        active: compactMouse.containsMouse

170
        MouseArea {
171
            id: compactMouse
172
            anchors.fill: parent
173
            hoverEnabled: true
174
            acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton
175
176
177
178
179
180
181
182
183

            onWheel: {
                var service = mpris2Source.serviceForSource(mpris2Source.current)
                var operation = service.operationDescription("ChangeVolume")
                operation.delta = (wheel.angleDelta.y / 120) * 0.03
                operation.showOSD = true
                service.startOperationCall(operation)
            }

184
            onClicked: {
185
186
                switch (mouse.button) {
                case Qt.MiddleButton:
187
                    root.togglePlaying()
188
189
190
191
192
193
194
195
                    break
                case Qt.BackButton:
                    root.action_previous()
                    break
                case Qt.ForwardButton:
                    root.action_next()
                    break
                default:
196
197
198
199
200
201
                    plasmoid.expanded = !plasmoid.expanded
                }
            }
        }
    }

202
203
    PlasmaCore.DataSource {
        id: mpris2Source
204
205
206
207

        readonly property string multiplexSource: "@multiplex"
        property string current: multiplexSource

208
209
        readonly property var currentData: data[current]

210
        engine: "mpris2"
211
212
213
214
215
        connectedSources: sources

        onSourceAdded: {
            updateMprisSourcesModel()
        }
216

217
218
219
220
221
        onSourceRemoved: {
            // if player is closed, reset to multiplex source
            if (source === current) {
                current = multiplexSource
            }
222
            updateMprisSourcesModel()
223
        }
224
225
    }

226
    Component.onCompleted: {
227
228
        mpris2Source.serviceForSource("@multiplex").enableGlobalShortcuts()
        updateMprisSourcesModel()
229
230
    }

231
232
233
234
235
236
237
238
239
240
241
242
    function togglePlaying() {
        if (root.state === "playing") {
            if (root.canPause) {
                root.action_pause();
            }
        } else {
            if (root.canPlay) {
                root.action_play();
            }
        }
    }

243
    function action_open() {
244
        serviceOp(mpris2Source.current, "Raise");
245
    }
246
    function action_quit() {
247
248
        serviceOp(mpris2Source.current, "Quit");
    }
249

250
251
252
253
254
255
256
257
258
    function action_play() {
        serviceOp(mpris2Source.current, "Play");
    }

    function action_pause() {
        serviceOp(mpris2Source.current, "Pause");
    }

    function action_playPause() {
259
        serviceOp(mpris2Source.current, "PlayPause");
260
261
    }

262
    function action_previous() {
263
        serviceOp(mpris2Source.current, "Previous");
264
265
    }

266
    function action_next() {
267
        serviceOp(mpris2Source.current, "Next");
268
269
    }

270
    function action_stop() {
271
272
273
        serviceOp(mpris2Source.current, "Stop");
    }

274
275
276
277
278
279
    function serviceOp(src, op) {
        var service = mpris2Source.serviceForSource(src);
        var operation = service.operationDescription(op);
        return service.startOperationCall(operation);
    }

280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
    function updateMprisSourcesModel () {

        var model = [{
            'text': i18n("Choose player automatically"),
            'icon': 'emblem-favorite',
            'source': mpris2Source.multiplexSource
        }]

        var sources = mpris2Source.sources
        for (var i = 0, length = sources.length; i < length; ++i) {
            var source = sources[i]
            if (source === mpris2Source.multiplexSource) {
                continue
            }

            model.push({
                'text': mpris2Source.data[source]["Identity"],
                'icon': mpris2Source.data[source]["Desktop Icon Name"] || mpris2Source.data[source]["Desktop Entry"] || source,
                'source': source
            });
        }

        root.mprisSourcesModel = model;
    }

305
306
307
    states: [
        State {
            name: "playing"
308
            when: !root.noPlayer && mpris2Source.currentData.PlaybackStatus === "Playing"
309

310
311
312
            PropertyChanges {
                target: plasmoid
                toolTipMainText: track
313
                toolTipSubText: artist ? i18nc("by Artist (player name)", "by %1 (%2)", artist, identity) : identity
314
            }
315
316
317
        },
        State {
            name: "paused"
318
            when: !root.noPlayer && mpris2Source.currentData.PlaybackStatus === "Paused"
319

320
321
322
            PropertyChanges {
                target: plasmoid
                toolTipMainText: track
323
                toolTipSubText: artist ? i18nc("by Artist (paused, player name)", "by %1 (paused, %2)", artist, identity) : i18nc("Paused (player name)", "Paused (%1)", identity)
324
            }
325
326
        }
    ]
Lukáš Tinkl's avatar
Lukáš Tinkl committed
327
}