19816523c7
Media notifications currently always show the forward and backward buttons as clickable, regardless of whether the corresponding action is available or not. Media players usually address this by rendering controls insensitive in this case, so do the same here. https://bugzilla.gnome.org/show_bug.cgi?id=773884
288 lines
8.8 KiB
JavaScript
288 lines
8.8 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="CanGoNext" type="b" access="read" /> \
|
|
<property name="CanGoPrevious" type="b" access="read" /> \
|
|
<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._prevButton = 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._nextButton = 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();
|
|
},
|
|
|
|
_updateNavButton: function(button, sensitive) {
|
|
button.reactive = sensitive;
|
|
},
|
|
|
|
_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;
|
|
|
|
this._updateNavButton(this._prevButton, this._player.canGoPrevious);
|
|
this._updateNavButton(this._nextButton, this._player.canGoNext);
|
|
}
|
|
});
|
|
|
|
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();
|
|
},
|
|
|
|
get canGoNext() {
|
|
return this._playerProxy.CanGoNext;
|
|
},
|
|
|
|
next: function() {
|
|
this._playerProxy.NextRemote();
|
|
},
|
|
|
|
get canGoPrevious() {
|
|
return this._playerProxy.CanGoPrevious;
|
|
},
|
|
|
|
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();
|
|
|
|
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);
|
|
}
|
|
});
|