3ecdfaffd2
We lost media controls outside of notification banners when implementing the new notification designs. Reimplement this functionality as a dedicated "Media" section in the message list based on MPRIS. https://bugzilla.gnome.org/show_bug.cgi?id=756491
271 lines
8.3 KiB
JavaScript
271 lines
8.3 KiB
JavaScript
const Gio = imports.gi.Gio;
|
|
const Lang = imports.lang;
|
|
const Signals = imports.signals;
|
|
const Shell = imports.gi.Shell;
|
|
const St = imports.gi.St;
|
|
|
|
const Calendar = imports.ui.calendar;
|
|
const Main = imports.ui.main;
|
|
const MessageList = imports.ui.messageList;
|
|
|
|
const DBusIface = '<node> \
|
|
<interface name="org.freedesktop.DBus"> \
|
|
<method name="ListNames"> \
|
|
<arg type="as" direction="out" name="names" /> \
|
|
</method> \
|
|
<signal name="NameOwnerChanged"> \
|
|
<arg type="s" direction="out" name="name" /> \
|
|
<arg type="s" direction="out" name="oldOwner" /> \
|
|
<arg type="s" direction="out" name="newOwner" /> \
|
|
</signal> \
|
|
</interface> \
|
|
</node>';
|
|
const DBusProxy = Gio.DBusProxy.makeProxyWrapper(DBusIface);
|
|
|
|
const MprisIface = '<node> \
|
|
<interface name="org.mpris.MediaPlayer2"> \
|
|
<method name="Raise" /> \
|
|
<property name="CanRaise" type="b" access="read" /> \
|
|
<property name="DesktopEntry" type="s" access="read" /> \
|
|
</interface> \
|
|
</node>';
|
|
const MprisProxy = Gio.DBusProxy.makeProxyWrapper(MprisIface);
|
|
|
|
const MprisPlayerIface = '<node> \
|
|
<interface name="org.mpris.MediaPlayer2.Player"> \
|
|
<method name="PlayPause" /> \
|
|
<method name="Next" /> \
|
|
<method name="Previous" /> \
|
|
<property name="CanPlay" type="b" access="read" /> \
|
|
<property name="Metadata" type="a{sv}" access="read" /> \
|
|
<property name="PlaybackStatus" type="s" access="read" /> \
|
|
</interface> \
|
|
</node>';
|
|
const MprisPlayerProxy = Gio.DBusProxy.makeProxyWrapper(MprisPlayerIface);
|
|
|
|
const MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.';
|
|
|
|
const MediaMessage = new Lang.Class({
|
|
Name: 'MediaMessage',
|
|
Extends: MessageList.Message,
|
|
|
|
_init: function(player) {
|
|
this._player = player;
|
|
|
|
this.parent('', '');
|
|
|
|
this._icon = new St.Icon({ style_class: 'media-message-cover-icon' });
|
|
this.setIcon(this._icon);
|
|
|
|
this.addMediaControl('media-skip-backward-symbolic',
|
|
Lang.bind(this, function() {
|
|
this._player.previous();
|
|
}));
|
|
|
|
this._playPauseButton = this.addMediaControl(null,
|
|
Lang.bind(this, function() {
|
|
this._player.playPause();
|
|
}));
|
|
|
|
this.addMediaControl('media-skip-forward-symbolic',
|
|
Lang.bind(this, function() {
|
|
this._player.next();
|
|
}));
|
|
|
|
this._player.connect('changed', Lang.bind(this, this._update));
|
|
this._player.connect('closed', Lang.bind(this, this.close));
|
|
this._update();
|
|
},
|
|
|
|
_onClicked: function() {
|
|
this._player.raise();
|
|
Main.panel.closeCalendar();
|
|
},
|
|
|
|
_update: function() {
|
|
this.setTitle(this._player.trackArtists.join(', '));
|
|
this.setBody(this._player.trackTitle);
|
|
|
|
if (this._player.trackCoverUrl) {
|
|
let file = Gio.File.new_for_uri(this._player.trackCoverUrl);
|
|
this._icon.gicon = new Gio.FileIcon({ file: file });
|
|
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';
|
|
let iconName = isPlaying ? 'media-playback-pause-symbolic'
|
|
: 'media-playback-start-symbolic';
|
|
this._playPauseButton.child.icon_name = iconName;
|
|
}
|
|
});
|
|
|
|
const MprisPlayer = new Lang.Class({
|
|
Name: 'MprisPlayer',
|
|
|
|
_init: function(busName) {
|
|
this._mprisProxy = new MprisProxy(Gio.DBus.session, busName,
|
|
'/org/mpris/MediaPlayer2',
|
|
Lang.bind(this, this._onMprisProxyReady));
|
|
this._playerProxy = new MprisPlayerProxy(Gio.DBus.session, busName,
|
|
'/org/mpris/MediaPlayer2',
|
|
Lang.bind(this, this._onPlayerProxyReady));
|
|
|
|
this._visible = false;
|
|
this._trackArtists = [];
|
|
this._trackTitle = '';
|
|
this._trackCoverUrl = '';
|
|
},
|
|
|
|
get status() {
|
|
return this._playerProxy.PlaybackStatus;
|
|
},
|
|
|
|
get trackArtists() {
|
|
return this._trackArtists;
|
|
},
|
|
|
|
get trackTitle() {
|
|
return this._trackTitle;
|
|
},
|
|
|
|
get trackCoverUrl() {
|
|
return this._trackCoverUrl;
|
|
},
|
|
|
|
playPause: function() {
|
|
this._playerProxy.PlayPauseRemote();
|
|
},
|
|
|
|
next: function() {
|
|
this._playerProxy.NextRemote();
|
|
},
|
|
|
|
previous: function() {
|
|
this._playerProxy.PreviousRemote();
|
|
},
|
|
|
|
raise: function() {
|
|
// 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) {
|
|
let desktopId = this._mprisProxy.DesktopEntry + '.desktop';
|
|
app = Shell.AppSystem.get_default().lookup_app(desktopId);
|
|
}
|
|
|
|
if (app)
|
|
app.activate();
|
|
else if (this._mprisProxy.CanRaise)
|
|
this._mprisProxy.RaiseRemote();
|
|
},
|
|
|
|
_close: function() {
|
|
this._mprisProxy.disconnect(this._ownerNotifyId);
|
|
this._mprisProxy = null;
|
|
|
|
this._playerProxy.disconnect(this._propsChangedId);
|
|
this._playerProxy = null;
|
|
|
|
this.emit('closed');
|
|
},
|
|
|
|
_onMprisProxyReady: function() {
|
|
this._ownerNotifyId = this._mprisProxy.connect('notify::g-name-owner',
|
|
Lang.bind(this, function() {
|
|
if (!this._mprisProxy.g_name_owner)
|
|
this._close();
|
|
}));
|
|
},
|
|
|
|
_onPlayerProxyReady: function() {
|
|
this._propsChangedId = this._playerProxy.connect('g-properties-changed',
|
|
Lang.bind(this, this._updateState));
|
|
this._updateState();
|
|
},
|
|
|
|
_updateState: function() {
|
|
let metadata = {};
|
|
for (let prop in this._playerProxy.Metadata)
|
|
metadata[prop] = this._playerProxy.Metadata[prop].deep_unpack();
|
|
|
|
this._trackArtists = metadata['xesam:artist'] || [_("Unknown artist")];
|
|
this._trackTitle = metadata['xesam:title'] || _("Unknown title");
|
|
this._trackCoverUrl = metadata['mpris:artUrl'] || '';
|
|
this.emit('changed');
|
|
|
|
let visible = this._playerProxy.CanPlay;
|
|
|
|
if (this._visible != visible) {
|
|
this._visible = visible;
|
|
if (visible)
|
|
this.emit('show');
|
|
else
|
|
this._close();
|
|
}
|
|
}
|
|
});
|
|
Signals.addSignalMethods(MprisPlayer.prototype);
|
|
|
|
const MediaSection = new Lang.Class({
|
|
Name: 'MediaSection',
|
|
Extends: MessageList.MessageListSection,
|
|
|
|
_init: function() {
|
|
this.parent(_("Media"));
|
|
|
|
this._players = new Map();
|
|
|
|
this._proxy = new DBusProxy(Gio.DBus.session,
|
|
'org.freedesktop.DBus',
|
|
'/org/freedesktop/DBus',
|
|
Lang.bind(this, this._onProxyReady));
|
|
},
|
|
|
|
_shouldShow: function() {
|
|
return !this.empty && Calendar.isToday(this._date);
|
|
},
|
|
|
|
_addPlayer: function(busName) {
|
|
if (this._players.get(busName))
|
|
return;
|
|
|
|
let player = new MprisPlayer(busName);
|
|
player.connect('closed', Lang.bind(this,
|
|
function() {
|
|
this._players.delete(busName);
|
|
}));
|
|
player.connect('show', Lang.bind(this,
|
|
function() {
|
|
let message = new MediaMessage(player);
|
|
this.addMessage(message, true);
|
|
}));
|
|
this._players.set(busName, player);
|
|
},
|
|
|
|
_onProxyReady: function() {
|
|
this._proxy.ListNamesRemote(Lang.bind(this,
|
|
function([names]) {
|
|
names.forEach(Lang.bind(this,
|
|
function(name) {
|
|
if (!name.startsWith(MPRIS_PLAYER_PREFIX))
|
|
return;
|
|
|
|
this._addPlayer(name);
|
|
}));
|
|
}));
|
|
this._proxy.connectSignal('NameOwnerChanged',
|
|
Lang.bind(this, this._onNameOwnerChanged));
|
|
},
|
|
|
|
_onNameOwnerChanged: function(proxy, sender, [name, oldOwner, newOwner]) {
|
|
if (!name.startsWith(MPRIS_PLAYER_PREFIX))
|
|
return;
|
|
|
|
if (newOwner && !oldOwner)
|
|
this._addPlayer(name);
|
|
}
|
|
});
|