// Gnome Shell extension to control media playback // Copyright (C) 2011 by Timur Kristóf /* Licensed under GPLv2+ */ //TODO: detect available MPRIS players on D-Bus //TODO: disconnect when the player quits //TODO: ability to start a player with D-Bus activation const Gio = imports.gi.Gio; const Lang = imports.lang; const St = imports.gi.St; const Shell = imports.gi.Shell; const DBus = imports.dbus; const GLib = imports.gi.GLib; const Gettext = imports.gettext.domain('gnome-shell-extensions'); const _ = Gettext.gettext; const Main = imports.ui.main; const Panel = imports.ui.panel; const PanelMenu = imports.ui.panelMenu; const PopupMenu = imports.ui.popupMenu; // This is for debugging purposes function _debugStuff(str) { let text = new St.Label({ text: str, style: 'font-size: 10px; background: #000000; color: #ffffff;' }); let monitor = global.get_primary_monitor(); global.stage.add_actor(text); text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Mainloop.timeout_add(3000, function () { text.destroy(); }); } const MprisPlayerInterface = { name: 'org.mpris.MediaPlayer2.Player', properties: [{ name: 'PlaybackStatus', signature: 's', access: 'read' }, { name: 'Metadata', signature: 'a{sv}', access: 'read' }], methods: [{ name: 'Play', inSignature: '', outSignature: '' }, { name: 'Pause', inSignature: '', outSignature: '' }, { name: 'Previous', inSignature: '', outSignature: '' }, { name: 'Next', inSignature: '', outSignature: '' }], signals: [] }; let MprisPlayer = DBus.makeProxyClass(MprisPlayerInterface); const FreedesktopInterface = { name: 'org.freedesktop.DBus.Properties', properties: [], methods: [], signals: [{ name: 'PropertiesChanged', inSignature: '' }] }; let FreedesktopSignaler = DBus.makeProxyClass(FreedesktopInterface); const DBusInfoInterface = { name: 'org.mpris.MediaPlayer2.Player', properties: [], methods: [{ name: 'ListNames', inSignature: '', outSignature: 'a{s}' }], signals: [{ name: 'NameOwnerChanged', inSignature: '' }] }; let DBusInfo = DBus.makeProxyClass(DBusInfoInterface); function PopupMenuItemWithIcon() { this._init.apply(this, arguments); } PopupMenuItemWithIcon.prototype = { __proto__: PopupMenu.PopupBaseMenuItem.prototype, _init: function (text, iconName, params) { PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params); this.box = new St.BoxLayout(); this.icon = new St.Icon({ icon_type: St.IconType.FULLCOLOR, icon_size: 16, icon_name: iconName }); this.icon.set_style('padding: 0 5px;'); this.label = new St.Label({ text: text }); this.box.add(this.icon); this.box.add(this.label); this.addActor(this.box); } }; function MusicControl() { this._init(); } MusicControl.prototype = { __proto__: PanelMenu.SystemStatusButton.prototype, _init: function() { PanelMenu.SystemStatusButton.prototype._init.call(this, 'audio-x-generic'); /*this._dbusInfo = new DBusInfo();*/ this._player = new MprisPlayer(DBus.session, 'org.bansheeproject.Banshee', '/org/mpris/MediaPlayer2'); this._freedesktop = new FreedesktopSignaler(DBus.session, 'org.bansheeproject.Banshee', '/org/mpris/MediaPlayer2'); this._freedesktop.connect('PropertiesChanged', Lang.bind(this, this._playbackPropertiesChanged)); this._title = new PopupMenu.PopupMenuItem("Now playing...", { reactive: false }); this._title.label.set_style('font-size: 18px;'); this.menu.addMenuItem(this._title); this._info = new PopupMenu.PopupMenuItem("Please launch a media player", { reactive: false }); this.menu.addMenuItem(this._info); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this._playItem = new PopupMenuItemWithIcon('Play', 'media-playback-start-symbolic'); this._playItem.connect('activate', Lang.bind(this, this._togglePlay)); this.menu.addMenuItem(this._playItem); this._previousItem = new PopupMenuItemWithIcon('Previous', 'media-skip-backward-symbolic'); this._previousItem.connect('activate', Lang.bind(this, this._previous)); this.menu.addMenuItem(this._previousItem); this._nextItem = new PopupMenuItemWithIcon('Next', 'media-skip-forward-symbolic'); this._nextItem.connect('activate', Lang.bind(this, this._next)); this.menu.addMenuItem(this._nextItem); this.actor.show(); this.refresh(); }, refresh: function() { this._player.GetRemote('PlaybackStatus', Lang.bind(this, function(result, error) { if (result == 'Playing') { this._playItem.label.set_text('Pause'); this._playItem.icon.set_icon_name('media-playback-pause-symbolic'); } else { this._playItem.label.set_text('Play'); this._playItem.icon.set_icon_name('media-playback-start-symbolic'); } })); this._player.GetRemote('Metadata', Lang.bind(this, function(result, error) { this._title.label.set_text(result['xesam:title']); this._info.label.set_text(result['xesam:artist'] + ' - ' + result['xesam:album']); })); }, _playbackPropertiesChanged: function(emitter, data) { this.refresh(); }, _onHoverChanged: function() { }, _previous: function() { this._player.PreviousRemote(); }, _next: function() { this._player.NextRemote(); }, _togglePlay: function() { this._player.GetRemote('PlaybackStatus', Lang.bind(this, function(result, error) { if (result == 'Playing') this._player.PauseRemote(); else this._player.PlayRemote(); })); this.refresh(); } }; function main(metadata) { // This should be placed in the tray (bottom right), but I didn't find an example that does so. Panel.STANDARD_TRAY_ICON_ORDER.unshift('music-control'); Panel.STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['music-control'] = MusicControl; }