calendar: Add Media section
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
This commit is contained in:
parent
ee8fd1e613
commit
3ecdfaffd2
@ -849,6 +849,20 @@ StScrollBar {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
font-size: .9em; }
|
font-size: .9em; }
|
||||||
|
|
||||||
|
.message-media-control {
|
||||||
|
padding: 6px; }
|
||||||
|
.message-media-control:last-child:ltr {
|
||||||
|
padding-right: 18px; }
|
||||||
|
.message-media-control:last-child:rtl {
|
||||||
|
padding-left: 18px; }
|
||||||
|
|
||||||
|
.media-message-cover-icon {
|
||||||
|
icon-size: 32px; }
|
||||||
|
.media-message-cover-icon.fallback {
|
||||||
|
icon-size: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid black; }
|
||||||
|
|
||||||
.system-switch-user-submenu-icon.user-icon {
|
.system-switch-user-submenu-icon.user-icon {
|
||||||
icon-size: 20px;
|
icon-size: 20px;
|
||||||
padding: 0 2px; }
|
padding: 0 2px; }
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 9fb3918831459cd002f3d621494cf5eac70fe46a
|
Subproject commit 7e13533ab5280fd1370b3e9a7b8ba57a049cfb29
|
@ -849,6 +849,20 @@ StScrollBar {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
font-size: .9em; }
|
font-size: .9em; }
|
||||||
|
|
||||||
|
.message-media-control {
|
||||||
|
padding: 6px; }
|
||||||
|
.message-media-control:last-child:ltr {
|
||||||
|
padding-right: 18px; }
|
||||||
|
.message-media-control:last-child:rtl {
|
||||||
|
padding-left: 18px; }
|
||||||
|
|
||||||
|
.media-message-cover-icon {
|
||||||
|
icon-size: 32px; }
|
||||||
|
.media-message-cover-icon.fallback {
|
||||||
|
icon-size: 16px;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #1c1f1f; }
|
||||||
|
|
||||||
.system-switch-user-submenu-icon.user-icon {
|
.system-switch-user-submenu-icon.user-icon {
|
||||||
icon-size: 20px;
|
icon-size: 20px;
|
||||||
padding: 0 2px; }
|
padding: 0 2px; }
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
<file>ui/messageTray.js</file>
|
<file>ui/messageTray.js</file>
|
||||||
<file>ui/messageList.js</file>
|
<file>ui/messageList.js</file>
|
||||||
<file>ui/modalDialog.js</file>
|
<file>ui/modalDialog.js</file>
|
||||||
|
<file>ui/mpris.js</file>
|
||||||
<file>ui/notificationDaemon.js</file>
|
<file>ui/notificationDaemon.js</file>
|
||||||
<file>ui/osdWindow.js</file>
|
<file>ui/osdWindow.js</file>
|
||||||
<file>ui/osdMonitorLabeler.js</file>
|
<file>ui/osdMonitorLabeler.js</file>
|
||||||
|
@ -13,6 +13,7 @@ const Shell = imports.gi.Shell;
|
|||||||
const Main = imports.ui.main;
|
const Main = imports.ui.main;
|
||||||
const MessageList = imports.ui.messageList;
|
const MessageList = imports.ui.messageList;
|
||||||
const MessageTray = imports.ui.messageTray;
|
const MessageTray = imports.ui.messageTray;
|
||||||
|
const Mpris = imports.ui.mpris;
|
||||||
const Util = imports.misc.util;
|
const Util = imports.misc.util;
|
||||||
|
|
||||||
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
|
const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
@ -1099,6 +1100,9 @@ const CalendarMessageList = new Lang.Class({
|
|||||||
this._scrollView.add_actor(this._sectionList);
|
this._scrollView.add_actor(this._sectionList);
|
||||||
this._sections = new Map();
|
this._sections = new Map();
|
||||||
|
|
||||||
|
this._mediaSection = new Mpris.MediaSection();
|
||||||
|
this._addSection(this._mediaSection);
|
||||||
|
|
||||||
this._notificationSection = new NotificationSection();
|
this._notificationSection = new NotificationSection();
|
||||||
this._addSection(this._notificationSection);
|
this._addSection(this._notificationSection);
|
||||||
|
|
||||||
|
@ -324,6 +324,9 @@ const Message = new Lang.Class({
|
|||||||
vertical: true, x_expand: true });
|
vertical: true, x_expand: true });
|
||||||
hbox.add_actor(contentBox);
|
hbox.add_actor(contentBox);
|
||||||
|
|
||||||
|
this._mediaControls = new St.BoxLayout();
|
||||||
|
hbox.add_actor(this._mediaControls);
|
||||||
|
|
||||||
let titleBox = new St.BoxLayout();
|
let titleBox = new St.BoxLayout();
|
||||||
contentBox.add_actor(titleBox);
|
contentBox.add_actor(titleBox);
|
||||||
|
|
||||||
@ -405,6 +408,15 @@ const Message = new Lang.Class({
|
|||||||
this._actionBin.visible = this.expanded;
|
this._actionBin.visible = this.expanded;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addMediaControl: function(iconName, callback) {
|
||||||
|
let icon = new St.Icon({ icon_name: iconName, icon_size: 16 });
|
||||||
|
let button = new St.Button({ style_class: 'message-media-control',
|
||||||
|
child: icon });
|
||||||
|
button.connect('clicked', callback);
|
||||||
|
this._mediaControls.add_actor(button);
|
||||||
|
return button;
|
||||||
|
},
|
||||||
|
|
||||||
setExpandedBody: function(actor) {
|
setExpandedBody: function(actor) {
|
||||||
if (actor == null) {
|
if (actor == null) {
|
||||||
if (this._bodyStack.get_n_children() > 1)
|
if (this._bodyStack.get_n_children() > 1)
|
||||||
@ -476,7 +488,7 @@ const Message = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
canClose: function() {
|
canClose: function() {
|
||||||
return true;
|
return this._mediaControls.get_n_children() == 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
_sync: function() {
|
_sync: function() {
|
||||||
|
270
js/ui/mpris.js
Normal file
270
js/ui/mpris.js
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
@ -35,6 +35,7 @@ js/ui/lookingGlass.js
|
|||||||
js/ui/main.js
|
js/ui/main.js
|
||||||
js/ui/messageList.js
|
js/ui/messageList.js
|
||||||
js/ui/messageTray.js
|
js/ui/messageTray.js
|
||||||
|
js/ui/mpris.js
|
||||||
js/ui/notificationDaemon.js
|
js/ui/notificationDaemon.js
|
||||||
js/ui/overviewControls.js
|
js/ui/overviewControls.js
|
||||||
js/ui/overview.js
|
js/ui/overview.js
|
||||||
|
Loading…
Reference in New Issue
Block a user