diff options
-rw-r--r-- | src/media-player-mpris.vala | 22 | ||||
-rw-r--r-- | src/media-player.vala | 4 | ||||
-rw-r--r-- | src/mpris2-interfaces.vala | 5 | ||||
-rw-r--r-- | src/sound-menu.vala | 67 | ||||
-rw-r--r-- | tests/media-player-mock.vala | 6 | ||||
-rw-r--r-- | tests/sound-menu.cc | 190 |
6 files changed, 230 insertions, 64 deletions
diff --git a/src/media-player-mpris.vala b/src/media-player-mpris.vala index 741d887..408cdda 100644 --- a/src/media-player-mpris.vala +++ b/src/media-player-mpris.vala @@ -79,6 +79,24 @@ public class MediaPlayerMpris: MediaPlayer { } } + public override bool can_do_play { + get { + return this.proxy.CanPlay; + } + } + + public override bool can_do_prev { + get { + return this.proxy.CanGoPrevious; + } + } + + public override bool can_do_next { + get { + return this.proxy.CanGoNext; + } + } + /** * 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. @@ -272,6 +290,10 @@ public class MediaPlayerMpris: MediaPlayer { if (changed_properties.lookup ("PlaybackStatus", "s", null)) { this.state = this.proxy.PlaybackStatus != null ? this.proxy.PlaybackStatus : "Unknown"; } + if (changed_properties.lookup ("CanGoNext", "b", null) || changed_properties.lookup ("CanGoPrevious", "b", null) || + changed_properties.lookup ("CanPlay", "b", null) || changed_properties.lookup ("CanPause", "b", null)) { + this.playbackstatus_changed (); + } var metadata = changed_properties.lookup_value ("Metadata", new VariantType ("a{sv}")); if (metadata != null) diff --git a/src/media-player.vala b/src/media-player.vala index 4d4aef3..04d1426 100644 --- a/src/media-player.vala +++ b/src/media-player.vala @@ -26,6 +26,9 @@ public abstract class MediaPlayer : Object { public virtual bool is_running { get { not_implemented(); return false; } } public virtual bool can_raise { get { not_implemented(); return false; } } + public virtual bool can_do_next { get { not_implemented(); return false; } } + public virtual bool can_do_prev { get { not_implemented(); return false; } } + public virtual bool can_do_play { get { not_implemented(); return false; } } public class Track : Object { public string artist { get; construct; } @@ -44,6 +47,7 @@ public abstract class MediaPlayer : Object { } public signal void playlists_changed (); + public signal void playbackstatus_changed (); public abstract void activate (); public abstract void play_pause (); diff --git a/src/mpris2-interfaces.vala b/src/mpris2-interfaces.vala index a472d5c..0ed8719 100644 --- a/src/mpris2-interfaces.vala +++ b/src/mpris2-interfaces.vala @@ -37,7 +37,10 @@ public interface MprisPlayer : Object { // properties public abstract HashTable<string, Variant?> Metadata{owned get; set;} public abstract int32 Position{owned get; set;} - public abstract string? PlaybackStatus{owned get; set;} + public abstract string? PlaybackStatus{owned get; set;} + public abstract bool CanPlay{owned get; set;} + public abstract bool CanGoNext{owned get; set;} + public abstract bool CanGoPrevious{owned get; set;} // methods public abstract async void PlayPause() throws IOError; public abstract async void Next() throws IOError; diff --git a/src/sound-menu.vala b/src/sound-menu.vala index 8718162..7a6044b 100644 --- a/src/sound-menu.vala +++ b/src/sound-menu.vala @@ -157,18 +157,26 @@ public class SoundMenu: Object this.update_playlists (player); var handler_id = player.notify["is-running"].connect ( () => { - if (player.is_running) - if (this.find_player_section(player) == -1) + if (player.is_running) { + int index = this.find_player_section(player); + if (index == -1) { this.insert_player_section (player); - else + } + else { + update_player_section (player, index); + } + } + else { if (this.hide_inactive) this.remove_player_section (player); + } this.update_playlists (player); }); this.notify_handlers.insert (player, handler_id); player.playlists_changed.connect (this.update_playlists); + player.playbackstatus_changed.connect (this.update_playbackstatus); } public void remove_player (MediaPlayer player) { @@ -215,6 +223,23 @@ public class SoundMenu: Object return -1; } + MenuItem create_playback_menu_item (MediaPlayer player) { + var playback_item = new MenuItem (null, null); + playback_item.set_attribute ("x-canonical-type", "s", "com.canonical.unity.playback-item"); + if (player.is_running) { + if (player.can_do_play) { + playback_item.set_attribute ("x-canonical-play-action", "s", "indicator.play." + player.id); + } + if (player.can_do_next) { + playback_item.set_attribute ("x-canonical-next-action", "s", "indicator.next." + player.id); + } + if (player.can_do_prev) { + playback_item.set_attribute ("x-canonical-previous-action", "s", "indicator.previous." + player.id); + } + } + return playback_item; + } + void insert_player_section (MediaPlayer player) { if (this.hide_players) return; @@ -240,9 +265,21 @@ public class SoundMenu: Object var playback_item = new MenuItem (null, null); playback_item.set_attribute ("x-canonical-type", "s", "com.canonical.unity.playback-item"); - playback_item.set_attribute ("x-canonical-play-action", "s", "indicator.play." + player.id); - playback_item.set_attribute ("x-canonical-next-action", "s", "indicator.next." + player.id); - playback_item.set_attribute ("x-canonical-previous-action", "s", "indicator.previous." + player.id); + playback_item.set_attribute ("x-canonical-play-action", "s", "indicator.play." + player.id + ".disabled"); + playback_item.set_attribute ("x-canonical-next-action", "s", "indicator.next." + player.id + ".disabled"); + playback_item.set_attribute ("x-canonical-previous-action", "s", "indicator.previous." + player.id + ".disabled"); + + if (player.is_running) { + if (player.can_do_play) { + playback_item.set_attribute ("x-canonical-play-action", "s", "indicator.play." + player.id); + } + if (player.can_do_next) { + playback_item.set_attribute ("x-canonical-next-action", "s", "indicator.next." + player.id); + } + if (player.can_do_prev) { + playback_item.set_attribute ("x-canonical-previous-action", "s", "indicator.previous." + player.id); + } + } section.append_item (playback_item); /* Add new players to the end of the player sections, just before the settings */ @@ -262,6 +299,17 @@ public class SoundMenu: Object this.menu.remove (index); } + void update_player_section (MediaPlayer player, int index) { + var player_section = this.menu.get_item_link(index, Menu.LINK_SECTION) as Menu; + if (player_section.get_n_items () == 2) { + // we have 2 items, the second one is the playback item + // remove it first + player_section.remove (1); + MenuItem playback_item = create_playback_menu_item (player); + player_section.append_item (playback_item); + } + } + void update_playlists (MediaPlayer player) { int index = find_player_section (player); if (index < 0) @@ -292,6 +340,13 @@ public class SoundMenu: Object submenu.append_section (null, playlists_section); player_section.append_submenu (_("Choose Playlist"), submenu); } + + void update_playbackstatus (MediaPlayer player) { + int index = find_player_section (player); + if (index != -1) { + update_player_section (player, index); + } + } MenuItem create_slider_menu_item (string label, string action, double min, double max, double step, string min_icon_name, string max_icon_name) { var min_icon = new ThemedIcon.with_default_fallbacks (min_icon_name); diff --git a/tests/media-player-mock.vala b/tests/media-player-mock.vala index 14028f5..0c4ae80 100644 --- a/tests/media-player-mock.vala +++ b/tests/media-player-mock.vala @@ -28,6 +28,9 @@ public class MediaPlayerMock: MediaPlayer { public override bool is_running { get { return mock_is_running; } } public override bool can_raise { get { return mock_can_raise; } } + public override bool can_do_next { get { return mock_can_do_next; } } + public override bool can_do_prev { get { return mock_can_do_prev; } } + public override bool can_do_play { get { return mock_can_do_play; } } public override MediaPlayer.Track? current_track { get { return mock_current_track; } set { this.mock_current_track = value; } } @@ -40,6 +43,9 @@ public class MediaPlayerMock: MediaPlayer { public bool mock_is_running { get; set; } public bool mock_can_raise { get; set; } + public bool mock_can_do_next { get; set; } + public bool mock_can_do_prev { get; set; } + public bool mock_can_do_play { get; set; } public MediaPlayer.Track? mock_current_track { get; set; } diff --git a/tests/sound-menu.cc b/tests/sound-menu.cc index 10c0cb9..75a661b 100644 --- a/tests/sound-menu.cc +++ b/tests/sound-menu.cc @@ -27,87 +27,163 @@ extern "C" { class SoundMenuTest : public ::testing::Test { - protected: - GTestDBus * bus = nullptr; + protected: + GTestDBus * bus = nullptr; - virtual void SetUp() { - bus = g_test_dbus_new(G_TEST_DBUS_NONE); - g_test_dbus_up(bus); - } + virtual void SetUp() { + bus = g_test_dbus_new(G_TEST_DBUS_NONE); + g_test_dbus_up(bus); + } - virtual void TearDown() { - g_test_dbus_down(bus); - g_clear_object(&bus); - } + virtual void TearDown() { + g_test_dbus_down(bus); + g_clear_object(&bus); + } - void verify_item_attribute (GMenuModel * mm, guint index, const gchar * name, GVariant * value) { - g_variant_ref_sink(value); + void verify_item_attribute (GMenuModel * mm, guint index, const gchar * name, GVariant * value) { + g_variant_ref_sink(value); - gchar * variantstr = g_variant_print(value, TRUE); - g_debug("Expecting item %d to have a '%s' attribute: %s", index, name, variantstr); + gchar * variantstr = g_variant_print(value, TRUE); + g_debug("Expecting item %d to have a '%s' attribute: %s", index, name, variantstr); - const GVariantType * type = g_variant_get_type(value); - GVariant * itemval = g_menu_model_get_item_attribute_value(mm, index, name, type); + const GVariantType * type = g_variant_get_type(value); + GVariant * itemval = g_menu_model_get_item_attribute_value(mm, index, name, type); - ASSERT_NE(nullptr, itemval); - EXPECT_TRUE(g_variant_equal(itemval, value)); + ASSERT_NE(nullptr, itemval); + EXPECT_TRUE(g_variant_equal(itemval, value)); - g_variant_unref(value); - } + g_variant_unref(value); + } + + void verify_item_attribute_is_not_set(GMenuModel * mm, guint index, const gchar * name, const GVariantType * type) { + GVariant * itemval = g_menu_model_get_item_attribute_value(mm, index, name, type); + EXPECT_EQ(itemval, nullptr); + } + + void check_player_control_buttons(bool canPlay, bool canNext, bool canPrev) + { + SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE); + + MediaPlayerTrack * track = media_player_track_new("Artist", "Title", "Album", "http://art.url"); + + MediaPlayerMock * media = MEDIA_PLAYER_MOCK( + g_object_new(TYPE_MEDIA_PLAYER_MOCK, + "mock-id", "player-id", + "mock-name", "Test Player", + "mock-state", "Playing", + "mock-is-running", TRUE, + "mock-can-raise", FALSE, + "mock-current-track", track, + "mock-can-do-play", canPlay, + "mock-can-do-next", canNext, + "mock-can-do-prev", canPrev, + NULL) + ); + g_clear_object(&track); + + sound_menu_add_player(menu, MEDIA_PLAYER(media)); + + ASSERT_NE(nullptr, menu->menu); + EXPECT_EQ(2, g_menu_model_get_n_items(G_MENU_MODEL(menu->menu))); + + GMenuModel * section = g_menu_model_get_item_link(G_MENU_MODEL(menu->menu), 1, G_MENU_LINK_SECTION); + ASSERT_NE(nullptr, section); + EXPECT_EQ(2, g_menu_model_get_n_items(section)); /* No playlists, so two items */ + + /* Player display */ + verify_item_attribute(section, 0, "action", g_variant_new_string("indicator.player-id")); + verify_item_attribute(section, 0, "x-canonical-type", g_variant_new_string("com.canonical.unity.media-player")); + + /* Player control */ + verify_item_attribute(section, 1, "x-canonical-type", g_variant_new_string("com.canonical.unity.playback-item")); + verify_item_attribute(section, 1, "x-canonical-play-action", g_variant_new_string(canPlay ? "indicator.play.player-id" : "indicator.play.player-id.disabled")); + verify_item_attribute(section, 1, "x-canonical-next-action", g_variant_new_string(canNext ? "indicator.next.player-id" : "indicator.next.player-id.disabled")); + verify_item_attribute(section, 1, "x-canonical-previous-action", g_variant_new_string(canPrev ? "indicator.previous.player-id" : "indicator.previous.player-id.disabled")); + + g_clear_object(§ion); + + sound_menu_remove_player(menu, MEDIA_PLAYER(media)); + + EXPECT_EQ(1, g_menu_model_get_n_items(G_MENU_MODEL(menu->menu))); + + g_clear_object(&media); + g_clear_object(&menu); + } }; TEST_F(SoundMenuTest, BasicObject) { - SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE); + SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE); - ASSERT_NE(nullptr, menu); + ASSERT_NE(nullptr, menu); - g_clear_object(&menu); - return; + g_clear_object(&menu); + return; } TEST_F(SoundMenuTest, AddRemovePlayer) { - SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE); + SoundMenu * menu = sound_menu_new (nullptr, SOUND_MENU_DISPLAY_FLAGS_NONE); + + MediaPlayerTrack * track = media_player_track_new("Artist", "Title", "Album", "http://art.url"); - MediaPlayerTrack * track = media_player_track_new("Artist", "Title", "Album", "http://art.url"); + MediaPlayerMock * media = MEDIA_PLAYER_MOCK( + g_object_new(TYPE_MEDIA_PLAYER_MOCK, + "mock-id", "player-id", + "mock-name", "Test Player", + "mock-state", "Playing", + "mock-is-running", TRUE, + "mock-can-raise", FALSE, + "mock-current-track", track, + "mock-can-do-play", TRUE, + "mock-can-do-next", TRUE, + "mock-can-do-prev", TRUE, + NULL) + ); + g_clear_object(&track); - MediaPlayerMock * media = MEDIA_PLAYER_MOCK( - g_object_new(TYPE_MEDIA_PLAYER_MOCK, - "mock-id", "player-id", - "mock-name", "Test Player", - "mock-state", "Playing", - "mock-is-running", TRUE, - "mock-can-raise", FALSE, - "mock-current-track", track, - NULL) - ); - g_clear_object(&track); + sound_menu_add_player(menu, MEDIA_PLAYER(media)); - sound_menu_add_player(menu, MEDIA_PLAYER(media)); + ASSERT_NE(nullptr, menu->menu); + EXPECT_EQ(2, g_menu_model_get_n_items(G_MENU_MODEL(menu->menu))); - ASSERT_NE(nullptr, menu->menu); - EXPECT_EQ(2, g_menu_model_get_n_items(G_MENU_MODEL(menu->menu))); + GMenuModel * section = g_menu_model_get_item_link(G_MENU_MODEL(menu->menu), 1, G_MENU_LINK_SECTION); + ASSERT_NE(nullptr, section); + EXPECT_EQ(2, g_menu_model_get_n_items(section)); /* No playlists, so two items */ - GMenuModel * section = g_menu_model_get_item_link(G_MENU_MODEL(menu->menu), 1, G_MENU_LINK_SECTION); - ASSERT_NE(nullptr, section); - EXPECT_EQ(2, g_menu_model_get_n_items(section)); /* No playlists, so two items */ + /* Player display */ + verify_item_attribute(section, 0, "action", g_variant_new_string("indicator.player-id")); + verify_item_attribute(section, 0, "x-canonical-type", g_variant_new_string("com.canonical.unity.media-player")); - /* Player display */ - verify_item_attribute(section, 0, "action", g_variant_new_string("indicator.player-id")); - verify_item_attribute(section, 0, "x-canonical-type", g_variant_new_string("com.canonical.unity.media-player")); + /* Player control */ + verify_item_attribute(section, 1, "x-canonical-type", g_variant_new_string("com.canonical.unity.playback-item")); + verify_item_attribute(section, 1, "x-canonical-play-action", g_variant_new_string("indicator.play.player-id")); + verify_item_attribute(section, 1, "x-canonical-next-action", g_variant_new_string("indicator.next.player-id")); + verify_item_attribute(section, 1, "x-canonical-previous-action", g_variant_new_string("indicator.previous.player-id")); - /* Player control */ - verify_item_attribute(section, 1, "x-canonical-type", g_variant_new_string("com.canonical.unity.playback-item")); - verify_item_attribute(section, 1, "x-canonical-play-action", g_variant_new_string("indicator.play.player-id")); - verify_item_attribute(section, 1, "x-canonical-next-action", g_variant_new_string("indicator.next.player-id")); - verify_item_attribute(section, 1, "x-canonical-previous-action", g_variant_new_string("indicator.previous.player-id")); + g_clear_object(§ion); - g_clear_object(§ion); + sound_menu_remove_player(menu, MEDIA_PLAYER(media)); - sound_menu_remove_player(menu, MEDIA_PLAYER(media)); + EXPECT_EQ(1, g_menu_model_get_n_items(G_MENU_MODEL(menu->menu))); - EXPECT_EQ(1, g_menu_model_get_n_items(G_MENU_MODEL(menu->menu))); + g_clear_object(&media); + g_clear_object(&menu); + return; +} + +TEST_F(SoundMenuTest, AddRemovePlayerNoPlayNextPrev) { + check_player_control_buttons(false, false, false); +} - g_clear_object(&media); - g_clear_object(&menu); - return; +TEST_F(SoundMenuTest, AddRemovePlayerNoNext) { + check_player_control_buttons(true, false, true); } + +TEST_F(SoundMenuTest, AddRemovePlayerNoPrev) { + check_player_control_buttons(true, true, false); +} + +TEST_F(SoundMenuTest, AddRemovePlayerNoPlay) { + check_player_control_buttons(false, true, true); +} + +// |