From 3e0e21e246ab1033525d1760b4de4592e54b6dac Mon Sep 17 00:00:00 2001 From: Joshua Strobl Date: Tue, 11 May 2021 18:31:13 +0300 Subject: [PATCH] Implement Notification support. Fixes #8. Implemented Notification support using notify-send as opposed to libnotify. This is because notify_notification_send does a synchronous dbus call to org.freedesktop.Notification and typically results in DBus timeouts. In the near future we will rewrite this to just use our own proxy and do an async send. Refactored our GVariant metadata generation for a KotoIndexedTrack into a dedicated koto_indexed_track_get_metadata_vardict function. This can then be used across our playback engine and MPRIS. --- src/indexer/structs.h | 1 + src/indexer/track.c | 53 ++++++++++++++++++++++++++ src/main.c | 1 + src/playback/engine.c | 45 ++++++++++++++++++++++ src/playback/mpris.c | 87 +++++++------------------------------------ src/playback/mpris.h | 2 +- 6 files changed, 115 insertions(+), 74 deletions(-) diff --git a/src/indexer/structs.h b/src/indexer/structs.h index c50fd01..85c601f 100644 --- a/src/indexer/structs.h +++ b/src/indexer/structs.h @@ -111,6 +111,7 @@ KotoIndexedTrack* koto_indexed_track_new(KotoIndexedAlbum *album, const gchar *p KotoIndexedTrack* koto_indexed_track_new_with_uuid(const gchar *uuid); void koto_indexed_track_commit(KotoIndexedTrack *self); +GVariant* koto_indexed_track_get_metadata_vardict(KotoIndexedTrack *self); gchar* koto_indexed_track_get_uuid(KotoIndexedTrack *self); void koto_indexed_track_parse_name(KotoIndexedTrack *self); void koto_indexed_track_remove_from_playlist(KotoIndexedTrack *self, gchar *playlist_uuid); diff --git a/src/indexer/track.c b/src/indexer/track.c index 9ccc55c..68b08f7 100644 --- a/src/indexer/track.c +++ b/src/indexer/track.c @@ -278,6 +278,59 @@ void koto_indexed_track_commit(KotoIndexedTrack *self) { g_free(commit_op_errmsg); } +GVariant* koto_indexed_track_get_metadata_vardict(KotoIndexedTrack *self) { + if (!KOTO_IS_INDEXED_TRACK(self)) { + return NULL; + } + + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT); + + gchar *album_art_path = NULL; + gchar *album_name = NULL; + gchar *artist_name = NULL; + + KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid); + KotoIndexedAlbum *album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); + + g_object_get(album, + "art-path", &album_art_path, + "name", &album_name, + NULL); + + g_object_get(artist, + "name", &artist_name, + NULL); + + g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(self->uuid)); + + if (koto_utils_is_string_valid(album_art_path)) { // Valid album art path + album_art_path = g_strconcat("file://", album_art_path, NULL); // Prepend with file:// + g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new_string(album_art_path)); + } + + g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new_string(album_name)); + + if (koto_utils_is_string_valid(artist_name)) { // Valid artist name + GVariant *artist_name_variant; + GVariantBuilder *artist_list_builder = g_variant_builder_new(G_VARIANT_TYPE("as")); + g_variant_builder_add(artist_list_builder, "s", artist_name); + artist_name_variant = g_variant_new("as", artist_list_builder); + g_variant_builder_unref(artist_list_builder); + + g_variant_builder_add(builder, "{sv}", "xesam:artist", artist_name_variant); + g_variant_builder_add(builder, "{sv}", "playbackengine:artist", g_variant_new_string(artist_name)); // Add a sort of "meta" string val for our playback engine so we don't need to mess about with the array + } + + g_variant_builder_add(builder, "{sv}", "xesam:discNumber", g_variant_new_uint64(GPOINTER_TO_UINT(self->cd))); + g_variant_builder_add(builder, "{sv}", "xesam:title", g_variant_new_string(self->parsed_name)); + g_variant_builder_add(builder, "{sv}", "xesam:url", g_variant_new_string(self->path)); + g_variant_builder_add(builder, "{sv}", "xesam:trackNumber", g_variant_new_uint64(GPOINTER_TO_UINT(self->position))); + + GVariant *metadata_ret = g_variant_builder_end(builder); + + return metadata_ret; +} + gchar* koto_indexed_track_get_uuid(KotoIndexedTrack *self) { if (!KOTO_IS_INDEXED_TRACK(self)) { return NULL; diff --git a/src/main.c b/src/main.c index f6f28ba..106416d 100644 --- a/src/main.c +++ b/src/main.c @@ -17,6 +17,7 @@ #include #include +#include #include "db/cartographer.h" #include "db/db.h" #include "playback/media-keys.h" diff --git a/src/playback/engine.c b/src/playback/engine.c index f5f600a..1d3805d 100644 --- a/src/playback/engine.c +++ b/src/playback/engine.c @@ -18,9 +18,12 @@ #include #include #include +#include +#include #include "../db/cartographer.h" #include "../playlist/current.h" #include "../indexer/structs.h" +#include "../koto-utils.h" #include "engine.h" #include "mpris.h" @@ -54,6 +57,7 @@ struct _KotoPlaybackEngine { GstQuery *position_query; KotoIndexedTrack *current_track; + NotifyNotification *track_notification; gboolean is_muted; gboolean is_repeat_enabled; @@ -426,8 +430,49 @@ void koto_playback_engine_set_track_by_uuid(KotoPlaybackEngine *self, gchar *tra koto_playback_engine_set_position(self, 0); koto_playback_engine_set_volume(self, self->volume); // Re-enforce our volume on the updated playbin + GVariant *metadata = koto_indexed_track_get_metadata_vardict(track); // Get the GVariantBuilder variable dict for the metadata + GVariantDict *metadata_dict = g_variant_dict_new(metadata); + g_signal_emit(self, playback_engine_signals[SIGNAL_TRACK_CHANGE], 0); // Emit our track change signal koto_update_mpris_info_for_track(self->current_track); + + GVariant *track_name_var = g_variant_dict_lookup_value(metadata_dict, "xesam:title", NULL); // Get the GVariant for the name of the track + const gchar *track_name = g_variant_get_string(track_name_var, NULL); // Get the string of the track name + + GVariant *album_name_var = g_variant_dict_lookup_value(metadata_dict, "xesam:album", NULL); // Get the GVariant for the album name + const gchar *album_name = g_variant_get_string(album_name_var, NULL); // Get the string for the album name + + GVariant *artist_name_var = g_variant_dict_lookup_value(metadata_dict, "playbackengine:artist", NULL); // Get the GVariant for the name of the artist + const gchar *artist_name = g_variant_get_string(artist_name_var, NULL); // Get the string for the artist name + + gchar *artist_album_combo = g_strjoin(" - ", artist_name, album_name, NULL); // Join artist and album name separated by " - " + + gchar *icon_name = "audio-x-generic-symbolic"; + + if (g_variant_dict_contains(metadata_dict, "mpris:artUrl")) { // If we have artwork specified + GVariant *art_url_var = g_variant_dict_lookup_value(metadata_dict, "mpris:artUrl", NULL); // Get the GVariant for the art URL + const gchar *art_uri = g_variant_get_string(art_url_var, NULL); // Get the string for the artwork + icon_name = koto_utils_replace_string_all(g_strdup(art_uri), "file://", ""); + } + + // Super important note: We are not using libnotify directly because the synchronous nature of notify_notification_send seems to result in dbus timeouts + if (g_find_program_in_path("notify-send") != NULL) { // Have notify-send + char *argv[12]; + argv[0] = "notify-send"; + argv[1] = "-a"; + argv[2] = "Koto"; + argv[3] = "-i"; + argv[4] = icon_name; + argv[5] = "-c"; + argv[6] = "x-gnome.music"; + argv[7] = "-h"; + argv[8] = "string:desktop-entry:com.github.joshstrobl.koto"; + argv[9] = g_strdup(track_name); + argv[10] = artist_album_combo; + argv[11] = NULL; + + g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); + } } void koto_playback_engine_set_volume(KotoPlaybackEngine *self, gdouble volume) { diff --git a/src/playback/mpris.c b/src/playback/mpris.c index 4f995c0..cb4c11d 100644 --- a/src/playback/mpris.c +++ b/src/playback/mpris.c @@ -200,15 +200,14 @@ GVariant* handle_get_property( } if (g_strcmp0(property_name, "Metadata") == 0) { // Metadata - GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT); - KotoIndexedTrack *current_track = koto_playback_engine_get_current_track(playback_engine); if (KOTO_IS_INDEXED_TRACK(current_track)) { // Currently playing a track - koto_push_track_info_to_builder(builder, current_track); + ret = koto_indexed_track_get_metadata_vardict(current_track); + } else { // No track + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT); // Create an empty builder + ret = g_variant_builder_end(builder); // return the vardict } - - ret = g_variant_builder_end(builder); } if ( @@ -277,69 +276,6 @@ gboolean handle_set_property( return FALSE; } -void koto_push_track_info_to_builder(GVariantBuilder *builder, KotoIndexedTrack *track) { - if (!KOTO_IS_INDEXED_TRACK(track)) { - return; - } - - gchar *album_art_path = NULL; - gchar *album_name = NULL; - gchar *album_uuid = NULL; - gchar *artist_uuid = NULL; - gchar *artist_name = NULL; - gchar *track_name = NULL; - gchar *track_path = NULL; - gchar *track_uuid = NULL; - guint track_position = 0; - guint track_disc = 0; - - g_object_get(track, - "album-uuid", &album_uuid, - "artist-uuid", &artist_uuid, - "cd", &track_disc, - "path", &track_path, - "parsed-name", &track_name, - "position", &track_position, - "uuid", &track_uuid, - NULL); - - KotoIndexedArtist *artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); - KotoIndexedAlbum *album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); - - g_object_get(album, - "art-path", &album_art_path, - "name", &album_name, - NULL); - - g_object_get(artist, - "name", &artist_name, - NULL); - - g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(track_uuid)); - - if (koto_utils_is_string_valid(album_art_path)) { // Valid album art path - album_art_path = g_strconcat("file://", album_art_path, NULL); // Prepend with file:// - g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new_string(album_art_path)); - } - - g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new_string(album_name)); - - if (koto_utils_is_string_valid(artist_name)) { // Valid artist name - GVariant *artist_name_variant; - GVariantBuilder *artist_list_builder = g_variant_builder_new(G_VARIANT_TYPE("as")); - g_variant_builder_add(artist_list_builder, "s", artist_name); - artist_name_variant = g_variant_new("as", artist_list_builder); - g_variant_builder_unref(artist_list_builder); - - g_variant_builder_add(builder, "{sv}", "xesam:artist", artist_name_variant); - } - - g_variant_builder_add(builder, "{sv}", "xesam:discNumber", g_variant_new_uint64(track_disc)); - g_variant_builder_add(builder, "{sv}", "xesam:title", g_variant_new_string(track_name)); - g_variant_builder_add(builder, "{sv}", "xesam:url", g_variant_new_string(track_path)); - g_variant_builder_add(builder, "{sv}", "xesam:trackNumber", g_variant_new_uint64(track_position)); -} - void koto_update_mpris_playback_state(GstState state) { GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); @@ -366,12 +302,17 @@ void koto_update_mpris_info_for_track(KotoIndexedTrack *track) { return; } - GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); - GVariantBuilder *metadata_builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT); + GVariant *metadata = koto_indexed_track_get_metadata_vardict(track); // Get the GVariantBuilder variable dict for the metadata + koto_update_mpris_info_for_track_with_metadata(track, metadata); +} - koto_push_track_info_to_builder(metadata_builder, track); - GVariant *metadata_ret = g_variant_builder_end(metadata_builder); - g_variant_builder_add(builder, "{sv}", "Metadata", metadata_ret); +void koto_update_mpris_info_for_track_with_metadata(KotoIndexedTrack *track, GVariant *metadata) { + if (!KOTO_IS_INDEXED_TRACK(track)) { + return; + } + + GVariantBuilder *builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); + g_variant_builder_add(builder, "{sv}", "Metadata", metadata); g_dbus_connection_emit_signal(dbus_conn, NULL, diff --git a/src/playback/mpris.h b/src/playback/mpris.h index ce15e02..097acd1 100644 --- a/src/playback/mpris.h +++ b/src/playback/mpris.h @@ -21,9 +21,9 @@ #include #include "../indexer/structs.h" -void koto_push_track_info_to_builder(GVariantBuilder *builder, KotoIndexedTrack *track); void koto_update_mpris_playback_state(GstState state); void koto_update_mpris_info_for_track(KotoIndexedTrack *track); +void koto_update_mpris_info_for_track_with_metadata(KotoIndexedTrack *track, GVariant *metadata); void handle_method_call(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *method_name, GVariant *parameters, GDBusMethodInvocation *invocation, gpointer user_data); GVariant* handle_get_property(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data); gboolean handle_set_property(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GVariant *value, GError **error, gpointer user_data);