From 1419ef39b81be9d8a42b915ad9f56e81ab4eb03c Mon Sep 17 00:00:00 2001 From: Lars Uebernickel Date: Thu, 28 Mar 2013 12:34:35 -0400 Subject: Show running media players in the menu Each player has its own action with a dictionary state. Right now, this state only contains one key "running", which signifies whether an instance of the player is currently running. It does not yet show non-running players on startup, and ignores the blacklist. --- src/Makefile.am | 2 + src/media-player-list.vala | 70 +++++++++++++++++++++++++ src/media-player.vala | 124 +++++++++++++++++++++++++++++++++++++++++++++ src/service.vala | 40 ++++++++++++++- 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 src/media-player-list.vala create mode 100644 src/media-player.vala diff --git a/src/Makefile.am b/src/Makefile.am index 2421683..1de360c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,6 +20,8 @@ music_bridge_VALASOURCES = \ service.vala \ main.vala \ volume-control.vala \ + media-player.vala \ + media-player-list.vala \ music-player-bridge.vala \ transport-menu-item.vala \ specific-items-manager.vala \ diff --git a/src/media-player-list.vala b/src/media-player-list.vala new file mode 100644 index 0000000..0c0a212 --- /dev/null +++ b/src/media-player-list.vala @@ -0,0 +1,70 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Authors: + * Lars Uebernickel + */ + +/** + * MediaPlayerList is a list of media players that should appear in the sound menu. Its main responsibility is + * to listen for MPRIS players on the bus and attach them to the corresponding %Player objects. + */ +public class MediaPlayerList { + + public MediaPlayerList () { + this._players = new HashTable (str_hash, str_equal); + + this.mpris_watcher = new Mpris2Watcher (); + this.mpris_watcher.client_appeared.connect (this.player_appeared); + this.mpris_watcher.client_disappeared.connect (this.player_disappeared); + } + + public List players { + owned get { + return this._players.get_values (); + } + } + + public signal void player_added (MediaPlayer player); + + HashTable _players; + Mpris2Watcher mpris_watcher; + + void player_appeared (string desktop_id, string dbus_name, bool use_playlists) { + var appinfo = new DesktopAppInfo (desktop_id + ".desktop"); + if (appinfo == null) { + warning ("unable to find application '%s'", desktop_id); + return; + } + + MediaPlayer? player = this._players.lookup (desktop_id); + if (player == null) { + player = new MediaPlayer (appinfo); + this._players.insert (player.id, player); + this.player_added (player); + } + + player.attach (dbus_name); + } + + void player_disappeared (string dbus_name) { + MediaPlayer? player = this._players.find ( (name, player) => { + return player.dbus_name == dbus_name; + }); + + if (player != null) + player.detach (); + } +} diff --git a/src/media-player.vala b/src/media-player.vala new file mode 100644 index 0000000..8037ddb --- /dev/null +++ b/src/media-player.vala @@ -0,0 +1,124 @@ +/* + * Copyright 2013 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + * + * Authors: + * Lars Uebernickel + */ + +/** + * MediaPlayer represents an MRPIS-capable media player. + */ +public class MediaPlayer: Object { + + public MediaPlayer (DesktopAppInfo appinfo) { + this.appinfo = appinfo; + } + + /** Desktop id of the player */ + public string id { + get { + return this.appinfo.get_id (); + } + } + + /** Display name of the player */ + public string name { + get { + return this.appinfo.get_name (); + } + } + + /** Application icon of the player */ + public Icon icon { + get { + return this.appinfo.get_icon (); + } + } + + /** + * True if an instance of the player is currently running. + * + * See also: attach(), detach() + */ + public bool is_running { + get { + return this.proxy != null; + } + } + + /** Name of the player on the bus, if an instance is currently running */ + public string dbus_name { + get { + return this._dbus_name; + } + } + + /** + * Attach this object to a process of the associated media player. The player must own @dbus_name and + * implement the org.mpris.MediaPlayer2.Player interface. + * + * Only one player can be attached at any given time. Use detach() to detach a player. + * + * This method does not block. If it is successful, "is-running" will be set to %TRUE. + */ + public void attach (string dbus_name) { + return_if_fail (this._dbus_name == null && this.proxy == null); + + this._dbus_name = dbus_name; + Bus.get_proxy.begin (BusType.SESSION, dbus_name, "/org/mpris/MediaPlayer2", + DBusProxyFlags.NONE, null, got_proxy); + } + + /** + * Detach this object from a process running the associated media player. + * + * See also: attach() + */ + public void detach () { + this.proxy = null; + this._dbus_name = null; + this.notify_property ("is-running"); + } + + /** + * Launch the associated media player. + * + * Note: this will _not_ call attach(), because it doesn't know on which dbus-name the player will appear. + * Use attach() to attach this object to a running instance of the player. + */ + public void launch () { + try { + this.appinfo.launch (null, null); + } + catch (Error e) { + warning ("unable to launch %s: %s", appinfo.get_name (), e.message); + } + } + + DesktopAppInfo appinfo; + MprisPlayer? proxy; + string _dbus_name; + + void got_proxy (Object? obj, AsyncResult res) { + try { + this.proxy = Bus.get_proxy.end (res); + this.notify_property ("is-running"); + } + catch (Error e) { + this._dbus_name = null; + warning ("unable to attach to media player: %s", e.message); + } + } +} diff --git a/src/service.vala b/src/service.vala index aa6664e..4ea4a95 100644 --- a/src/service.vala +++ b/src/service.vala @@ -2,6 +2,9 @@ public class IndicatorSound.Service { public Service () { this.volume_control = new VolumeControl (); + + this.players = new MediaPlayerList (); + this.players.player_added.connect (player_added); } public int run () { @@ -28,6 +31,8 @@ public class IndicatorSound.Service { SimpleActionGroup actions; Menu menu; VolumeControl volume_control; + MediaPlayerList players; + uint player_action_update_id; void activate_settings (SimpleAction action, Variant? param) { try { @@ -41,7 +46,7 @@ public class IndicatorSound.Service { var submenu = new Menu (); submenu.append ("Mute", "indicator.mute"); - var slider = new MenuItem ("null", "indicator.volume"); + var slider = new MenuItem (null, "indicator.volume"); slider.set_attribute ("x-canonical-type", "s", "com.canonical.unity.slider"); submenu.append_item (slider); @@ -111,4 +116,37 @@ public class IndicatorSound.Service { void name_lost (DBusConnection connection, string name) { this.loop.quit (); } + + bool update_player_action (MediaPlayer player) { + var builder = new VariantBuilder (new VariantType ("a{sv}")); + builder.add ("{sv}", "running", new Variant ("b", player.is_running)); + var state = builder.end (); + + SimpleAction? action = this.actions.lookup (player.id) as SimpleAction; + if (action == null) { + action = new SimpleAction.stateful (player.id, null, state); + action.activate.connect ( () => { player.launch (); }); + this.actions.insert (action); + } + else { + action.set_state (state); + } + + this.player_action_update_id = 0; + return false; + } + + void eventually_update_player_action (MediaPlayer player) { + if (player_action_update_id == 0) + this.player_action_update_id = Idle.add ( () => this.update_player_action (player) ); + } + + void player_added (MediaPlayer player) { + var item = new MenuItem (player.name, player.id); + item.set_attribute ("x-canonical-type", "s", "com.canonical.unity.media-player"); + this.menu.insert_item (this.menu.get_n_items () -1, item); + + eventually_update_player_action (player); + player.notify.connect ( () => eventually_update_player_action (player) ); + } } -- cgit v1.2.3