2019-01-31 14:07:06 +00:00
|
|
|
/* exported MediaSection */
|
2019-07-16 09:24:13 +00:00
|
|
|
const { Gio, GObject, Shell, St } = imports.gi;
|
2016-02-15 11:13:22 +00:00
|
|
|
const Signals = imports.signals;
|
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
const MessageList = imports.ui.messageList;
|
|
|
|
|
2018-09-06 00:55:20 +00:00
|
|
|
const { loadInterfaceXML } = imports.misc.fileUtils;
|
|
|
|
|
|
|
|
const DBusIface = loadInterfaceXML('org.freedesktop.DBus');
|
2016-02-15 11:13:22 +00:00
|
|
|
const DBusProxy = Gio.DBusProxy.makeProxyWrapper(DBusIface);
|
|
|
|
|
2018-09-06 00:55:20 +00:00
|
|
|
const MprisIface = loadInterfaceXML('org.mpris.MediaPlayer2');
|
2016-02-15 11:13:22 +00:00
|
|
|
const MprisProxy = Gio.DBusProxy.makeProxyWrapper(MprisIface);
|
|
|
|
|
2018-09-06 00:55:20 +00:00
|
|
|
const MprisPlayerIface = loadInterfaceXML('org.mpris.MediaPlayer2.Player');
|
2016-02-15 11:13:22 +00:00
|
|
|
const MprisPlayerProxy = Gio.DBusProxy.makeProxyWrapper(MprisPlayerIface);
|
|
|
|
|
|
|
|
const MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.';
|
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
var MediaMessage = GObject.registerClass(
|
|
|
|
class MediaMessage extends MessageList.Message {
|
|
|
|
_init(player) {
|
|
|
|
super._init('', '');
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
this._player = player;
|
|
|
|
|
|
|
|
this._icon = new St.Icon({ style_class: 'media-message-cover-icon' });
|
|
|
|
this.setIcon(this._icon);
|
|
|
|
|
2021-02-04 01:17:08 +00:00
|
|
|
// reclaim space used by unused elements
|
|
|
|
this._secondaryBin.hide();
|
|
|
|
this._closeButton.hide();
|
|
|
|
|
2016-11-03 17:11:54 +00:00
|
|
|
this._prevButton = this.addMediaControl('media-skip-backward-symbolic',
|
2017-10-31 00:38:18 +00:00
|
|
|
() => {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._player.previous();
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
this._playPauseButton = this.addMediaControl(null,
|
2017-10-31 00:38:18 +00:00
|
|
|
() => {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._player.playPause();
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2016-11-03 17:11:54 +00:00
|
|
|
this._nextButton = this.addMediaControl('media-skip-forward-symbolic',
|
2017-10-31 00:38:18 +00:00
|
|
|
() => {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._player.next();
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2019-10-29 00:11:40 +00:00
|
|
|
this._updateHandlerId =
|
|
|
|
this._player.connect('changed', this._update.bind(this));
|
|
|
|
this._closedHandlerId =
|
|
|
|
this._player.connect('closed', this.close.bind(this));
|
2016-02-15 11:13:22 +00:00
|
|
|
this._update();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2019-10-29 00:11:40 +00:00
|
|
|
_onDestroy() {
|
|
|
|
super._onDestroy();
|
|
|
|
this._player.disconnect(this._updateHandlerId);
|
|
|
|
this._player.disconnect(this._closedHandlerId);
|
|
|
|
}
|
|
|
|
|
2019-09-10 05:42:48 +00:00
|
|
|
vfunc_clicked() {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._player.raise();
|
|
|
|
Main.panel.closeCalendar();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_updateNavButton(button, sensitive) {
|
2016-11-03 17:11:54 +00:00
|
|
|
button.reactive = sensitive;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-11-03 17:11:54 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_update() {
|
2020-10-24 15:07:10 +00:00
|
|
|
this.setTitle(this._player.trackTitle);
|
|
|
|
this.setBody(this._player.trackArtists.join(', '));
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
if (this._player.trackCoverUrl) {
|
|
|
|
let file = Gio.File.new_for_uri(this._player.trackCoverUrl);
|
2019-08-19 19:06:04 +00:00
|
|
|
this._icon.gicon = new Gio.FileIcon({ file });
|
2016-02-15 11:13:22 +00:00
|
|
|
this._icon.remove_style_class_name('fallback');
|
|
|
|
} else {
|
|
|
|
this._icon.icon_name = 'audio-x-generic-symbolic';
|
|
|
|
this._icon.add_style_class_name('fallback');
|
|
|
|
}
|
|
|
|
|
|
|
|
let isPlaying = this._player.status == 'Playing';
|
2019-08-19 19:33:15 +00:00
|
|
|
let iconName = isPlaying
|
|
|
|
? 'media-playback-pause-symbolic'
|
|
|
|
: 'media-playback-start-symbolic';
|
2016-02-15 11:13:22 +00:00
|
|
|
this._playPauseButton.child.icon_name = iconName;
|
2016-11-03 17:11:54 +00:00
|
|
|
|
|
|
|
this._updateNavButton(this._prevButton, this._player.canGoPrevious);
|
|
|
|
this._updateNavButton(this._nextButton, this._player.canGoNext);
|
2016-02-15 11:13:22 +00:00
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 01:19:44 +00:00
|
|
|
var MprisPlayer = class MprisPlayer {
|
|
|
|
constructor(busName) {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._mprisProxy = new MprisProxy(Gio.DBus.session, busName,
|
|
|
|
'/org/mpris/MediaPlayer2',
|
2017-12-02 00:27:35 +00:00
|
|
|
this._onMprisProxyReady.bind(this));
|
2016-02-15 11:13:22 +00:00
|
|
|
this._playerProxy = new MprisPlayerProxy(Gio.DBus.session, busName,
|
|
|
|
'/org/mpris/MediaPlayer2',
|
2017-12-02 00:27:35 +00:00
|
|
|
this._onPlayerProxyReady.bind(this));
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
this._visible = false;
|
|
|
|
this._trackArtists = [];
|
|
|
|
this._trackTitle = '';
|
|
|
|
this._trackCoverUrl = '';
|
2019-10-29 00:06:40 +00:00
|
|
|
this._busName = busName;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
get status() {
|
|
|
|
return this._playerProxy.PlaybackStatus;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
get trackArtists() {
|
|
|
|
return this._trackArtists;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
get trackTitle() {
|
|
|
|
return this._trackTitle;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
get trackCoverUrl() {
|
|
|
|
return this._trackCoverUrl;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
playPause() {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._playerProxy.PlayPauseRemote();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2016-11-03 17:11:54 +00:00
|
|
|
get canGoNext() {
|
|
|
|
return this._playerProxy.CanGoNext;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-11-03 17:11:54 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
next() {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._playerProxy.NextRemote();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2016-11-03 17:11:54 +00:00
|
|
|
get canGoPrevious() {
|
|
|
|
return this._playerProxy.CanGoPrevious;
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-11-03 17:11:54 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
previous() {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._playerProxy.PreviousRemote();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
raise() {
|
2016-02-15 11:13:22 +00:00
|
|
|
// The remote Raise() method may run into focus stealing prevention,
|
|
|
|
// so prefer activating the app via .desktop file if possible
|
|
|
|
let app = null;
|
|
|
|
if (this._mprisProxy.DesktopEntry) {
|
2020-02-14 15:10:34 +00:00
|
|
|
let desktopId = '%s.desktop'.format(this._mprisProxy.DesktopEntry);
|
2016-02-15 11:13:22 +00:00
|
|
|
app = Shell.AppSystem.get_default().lookup_app(desktopId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (app)
|
|
|
|
app.activate();
|
|
|
|
else if (this._mprisProxy.CanRaise)
|
|
|
|
this._mprisProxy.RaiseRemote();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_close() {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._mprisProxy.disconnect(this._ownerNotifyId);
|
|
|
|
this._mprisProxy = null;
|
|
|
|
|
|
|
|
this._playerProxy.disconnect(this._propsChangedId);
|
|
|
|
this._playerProxy = null;
|
|
|
|
|
|
|
|
this.emit('closed');
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onMprisProxyReady() {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._ownerNotifyId = this._mprisProxy.connect('notify::g-name-owner',
|
2017-10-31 00:38:18 +00:00
|
|
|
() => {
|
2016-02-15 11:13:22 +00:00
|
|
|
if (!this._mprisProxy.g_name_owner)
|
|
|
|
this._close();
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2020-05-13 05:38:01 +00:00
|
|
|
// It is possible for the bus to disappear before the previous signal
|
|
|
|
// is connected, so we must ensure that the bus still exists at this
|
|
|
|
// point.
|
|
|
|
if (!this._mprisProxy.g_name_owner)
|
|
|
|
this._close();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onPlayerProxyReady() {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._propsChangedId = this._playerProxy.connect('g-properties-changed',
|
2017-12-02 00:27:35 +00:00
|
|
|
this._updateState.bind(this));
|
2016-02-15 11:13:22 +00:00
|
|
|
this._updateState();
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_updateState() {
|
2016-02-15 11:13:22 +00:00
|
|
|
let metadata = {};
|
|
|
|
for (let prop in this._playerProxy.Metadata)
|
|
|
|
metadata[prop] = this._playerProxy.Metadata[prop].deep_unpack();
|
|
|
|
|
2019-10-29 00:06:40 +00:00
|
|
|
// Validate according to the spec; some clients send buggy metadata:
|
|
|
|
// https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata
|
|
|
|
this._trackArtists = metadata['xesam:artist'];
|
|
|
|
if (!Array.isArray(this._trackArtists) ||
|
|
|
|
!this._trackArtists.every(artist => typeof artist === 'string')) {
|
2019-08-20 00:51:42 +00:00
|
|
|
if (typeof this._trackArtists !== 'undefined') {
|
2020-02-14 15:10:34 +00:00
|
|
|
log(('Received faulty track artist metadata from %s; ' +
|
|
|
|
'expected an array of strings, got %s (%s)').format(
|
|
|
|
this._busName, this._trackArtists, typeof this._trackArtists));
|
2019-08-20 00:51:42 +00:00
|
|
|
}
|
2019-10-29 00:06:40 +00:00
|
|
|
this._trackArtists = [_("Unknown artist")];
|
|
|
|
}
|
|
|
|
|
|
|
|
this._trackTitle = metadata['xesam:title'];
|
|
|
|
if (typeof this._trackTitle !== 'string') {
|
2019-08-20 00:51:42 +00:00
|
|
|
if (typeof this._trackTitle !== 'undefined') {
|
2020-02-14 15:10:34 +00:00
|
|
|
log(('Received faulty track title metadata from %s; ' +
|
|
|
|
'expected a string, got %s (%s)').format(
|
|
|
|
this._busName, this._trackTitle, typeof this._trackTitle));
|
2019-08-20 00:51:42 +00:00
|
|
|
}
|
2019-10-29 00:06:40 +00:00
|
|
|
this._trackTitle = _("Unknown title");
|
|
|
|
}
|
|
|
|
|
|
|
|
this._trackCoverUrl = metadata['mpris:artUrl'];
|
|
|
|
if (typeof this._trackCoverUrl !== 'string') {
|
2019-08-20 00:51:42 +00:00
|
|
|
if (typeof this._trackCoverUrl !== 'undefined') {
|
2020-02-14 15:10:34 +00:00
|
|
|
log(('Received faulty track cover art metadata from %s; ' +
|
|
|
|
'expected a string, got %s (%s)').format(
|
|
|
|
this._busName, this._trackCoverUrl, typeof this._trackCoverUrl));
|
2019-08-20 00:51:42 +00:00
|
|
|
}
|
2019-10-29 00:06:40 +00:00
|
|
|
this._trackCoverUrl = '';
|
|
|
|
}
|
|
|
|
|
2016-02-15 11:13:22 +00:00
|
|
|
this.emit('changed');
|
|
|
|
|
|
|
|
let visible = this._playerProxy.CanPlay;
|
|
|
|
|
|
|
|
if (this._visible != visible) {
|
|
|
|
this._visible = visible;
|
|
|
|
if (visible)
|
|
|
|
this.emit('show');
|
|
|
|
else
|
2019-10-29 00:11:40 +00:00
|
|
|
this.emit('hide');
|
2016-02-15 11:13:22 +00:00
|
|
|
}
|
|
|
|
}
|
2017-10-31 01:19:44 +00:00
|
|
|
};
|
2016-02-15 11:13:22 +00:00
|
|
|
Signals.addSignalMethods(MprisPlayer.prototype);
|
|
|
|
|
2019-07-16 09:24:13 +00:00
|
|
|
var MediaSection = GObject.registerClass(
|
|
|
|
class MediaSection extends MessageList.MessageListSection {
|
|
|
|
_init() {
|
|
|
|
super._init();
|
2016-02-15 11:13:22 +00:00
|
|
|
|
|
|
|
this._players = new Map();
|
|
|
|
|
|
|
|
this._proxy = new DBusProxy(Gio.DBus.session,
|
|
|
|
'org.freedesktop.DBus',
|
|
|
|
'/org/freedesktop/DBus',
|
2017-12-02 00:27:35 +00:00
|
|
|
this._onProxyReady.bind(this));
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2020-02-20 15:40:04 +00:00
|
|
|
get allowed() {
|
|
|
|
return !Main.sessionMode.isGreeter;
|
|
|
|
}
|
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_addPlayer(busName) {
|
2016-02-15 11:13:22 +00:00
|
|
|
if (this._players.get(busName))
|
|
|
|
return;
|
|
|
|
|
|
|
|
let player = new MprisPlayer(busName);
|
2019-11-17 15:24:52 +00:00
|
|
|
let message = null;
|
2017-10-31 00:38:18 +00:00
|
|
|
player.connect('closed',
|
|
|
|
() => {
|
2016-02-15 11:13:22 +00:00
|
|
|
this._players.delete(busName);
|
2017-10-31 00:38:18 +00:00
|
|
|
});
|
2019-10-29 00:11:40 +00:00
|
|
|
player.connect('show', () => {
|
2019-11-17 15:24:52 +00:00
|
|
|
message = new MediaMessage(player);
|
|
|
|
this.addMessage(message, true);
|
2019-10-29 00:11:40 +00:00
|
|
|
});
|
|
|
|
player.connect('hide', () => {
|
2019-11-17 15:24:52 +00:00
|
|
|
this.removeMessage(message, true);
|
|
|
|
message = null;
|
2019-10-29 00:11:40 +00:00
|
|
|
});
|
2019-11-17 15:24:52 +00:00
|
|
|
|
2016-02-15 11:13:22 +00:00
|
|
|
this._players.set(busName, player);
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onProxyReady() {
|
2017-10-31 00:38:18 +00:00
|
|
|
this._proxy.ListNamesRemote(([names]) => {
|
|
|
|
names.forEach(name => {
|
|
|
|
if (!name.startsWith(MPRIS_PLAYER_PREFIX))
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._addPlayer(name);
|
|
|
|
});
|
|
|
|
});
|
2016-02-15 11:13:22 +00:00
|
|
|
this._proxy.connectSignal('NameOwnerChanged',
|
2017-12-02 00:27:35 +00:00
|
|
|
this._onNameOwnerChanged.bind(this));
|
2017-10-31 01:19:44 +00:00
|
|
|
}
|
2016-02-15 11:13:22 +00:00
|
|
|
|
2017-10-31 00:03:21 +00:00
|
|
|
_onNameOwnerChanged(proxy, sender, [name, oldOwner, newOwner]) {
|
2016-02-15 11:13:22 +00:00
|
|
|
if (!name.startsWith(MPRIS_PLAYER_PREFIX))
|
|
|
|
return;
|
|
|
|
|
2019-11-17 15:24:52 +00:00
|
|
|
if (newOwner && !oldOwner)
|
2016-02-15 11:13:22 +00:00
|
|
|
this._addPlayer(name);
|
|
|
|
}
|
2019-07-16 09:24:13 +00:00
|
|
|
});
|