Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package mpv-mpris for openSUSE:Factory checked in at 2026-09-21 12:01:54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/mpv-mpris (Old) and /work/SRC/openSUSE:Factory/.mpv-mpris.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "mpv-mpris" Mon Sep 21 12:01:54 2026 rev:9 rq:1379065 version:1.3 Changes: -------- --- /work/SRC/openSUSE:Factory/mpv-mpris/mpv-mpris.changes 2026-01-08 15:27:15.072614183 +0100 +++ /work/SRC/openSUSE:Factory/.mpv-mpris.new.383539/mpv-mpris.changes 2026-09-21 12:02:57.903599835 +0200 @@ -1,0 +2,24 @@ +Sat Sep 19 20:22:58 UTC 2026 - Mia Herkt <[email protected]> + +- Update to 1.3 + * Use mpv’s audio-client-name as the player’s display name. + * Always include a unique instance suffix in the D-Bus player + name, and handle custom names containing special characters or + long strings. + * Fix play/pause toggling and report CanPlay and CanPause + correctly when mpv is idle. + * Fix seeking in locales that use decimal commas and improve seek + position precision. + * Use MPRIS-compliant track IDs and fix creation-date metadata + formatting. + * Fix a crash when freeing cached artwork and report the correct + MIME type for embedded images. + * Skip embedded artwork extraction from pipes and devices to + avoid consuming playback input. + * Fix YouTube short-link matching for thumbnails. + * Improve resource cleanup, memory management, and handling of + session bus initialization failures. + * Update tests for instance-suffixed player names and remove the + executable-permission requirement for test plugins. + +------------------------------------------------------------------- Old: ---- mpv-mpris-1.2.tar.gz New: ---- mpv-mpris-1.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ mpv-mpris.spec ++++++ --- /var/tmp/diff_new_pack.cKlx3C/_old 2026-09-21 12:02:58.713633807 +0200 +++ /var/tmp/diff_new_pack.cKlx3C/_new 2026-09-21 12:02:58.716633933 +0200 @@ -17,7 +17,7 @@ Name: mpv-mpris -Version: 1.2 +Version: 1.3 Release: 0 Summary: MPRIS plugin for mpv License: MIT ++++++ mpv-mpris-1.2.tar.gz -> mpv-mpris-1.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpv-mpris-1.2/mpris.c new/mpv-mpris-1.3/mpris.c --- old/mpv-mpris-1.2/mpris.c 2026-01-01 23:21:19.000000000 +0100 +++ new/mpv-mpris-1.3/mpris.c 2026-09-17 01:23:53.000000000 +0200 @@ -5,8 +5,6 @@ #include <inttypes.h> #include <string.h> - - static const char *introspection_xml = "<node>\n" " <interface name=\"org.mpris.MediaPlayer2\">\n" @@ -74,7 +72,11 @@ GMainLoop *loop; GMainContext *ctx; int wakeup_pipe[2]; - gint bus_id; + guint bus_id; + guint pending_bus_owners; + GSource *mpv_pipe_source; + GSource *timeout_source; + GSource *bus_retry_source; GDBusConnection *connection; GDBusInterfaceInfo *root_interface_info; GDBusInterfaceInfo *player_interface_info; @@ -92,6 +94,8 @@ gboolean events_setup; int64_t playlist_count; int64_t playlist_pos; + gchar *cached_path; + gchar *cached_art_url; } UserData; static const char *STATUS_PLAYING = "Playing"; @@ -100,10 +104,13 @@ static const char *LOOP_NONE = "None"; static const char *LOOP_TRACK = "Track"; static const char *LOOP_PLAYLIST = "Playlist"; +static const char *TRACK_PATH_PREFIX = "/mpv/mpris/Track/"; +static const char *NO_TRACK_ID = "/org/mpris/MediaPlayer2/TrackList/NoTrack"; -static void setup_mpv_event_sources(UserData *ud); +static gboolean setup_mpv_event_sources(UserData *ud); static gboolean can_go_next(UserData *ud); static gboolean can_go_previous(UserData *ud); +static gboolean can_play_pause(UserData *ud); static gchar *string_to_utf8(gchar *maybe_utf8) { @@ -265,7 +272,7 @@ } static const char *youtube_url_pattern = - "^https?:\\/\\/(?:youtu.be\\/|(?:www\\.)?youtube\\.com\\/watch\\?v=)(?<id>[a-zA-Z0-9_-]*)\\??.*"; + "^https?:\\/\\/(?:youtu\\.be\\/|(?:www\\.)?youtube\\.com\\/watch\\?v=)(?<id>[a-zA-Z0-9_-]*)\\??.*"; static GRegex *youtube_url_regex; @@ -292,6 +299,7 @@ static gchar* extract_embedded_art(AVFormatContext *context) { AVPacket *packet = NULL; + enum AVCodecID codec_id = AV_CODEC_ID_NONE; for (unsigned int i = 0; i < context->nb_streams; i++) { if (context->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC) { AVPacket *p = &context->streams[i]->attached_pic; @@ -299,6 +307,7 @@ // Skip the thumbnail if the size is bigger than 25MiB to avoid crashes if (p->size <= 25*0x100000) { packet = p; + codec_id = context->streams[i]->codecpar->codec_id; break; } } @@ -307,8 +316,27 @@ return NULL; } + const char *mime; + switch (codec_id) { + case AV_CODEC_ID_PNG: + mime = "image/png"; + break; + case AV_CODEC_ID_GIF: + mime = "image/gif"; + break; + case AV_CODEC_ID_WEBP: + mime = "image/webp"; + break; + case AV_CODEC_ID_BMP: + mime = "image/bmp"; + break; + default: + mime = "image/jpeg"; + break; + } + gchar *data = g_base64_encode(packet->data, packet->size); - gchar *img = g_strconcat("data:image/jpeg;base64,", data, NULL); + gchar *img = g_strconcat("data:", mime, ";base64,", data, NULL); g_free(data); return img; @@ -318,6 +346,12 @@ { gchar *out = NULL; AVFormatContext *context = NULL; + + // Do not let FFmpeg open pipes/devices/fd aliases: that can consume mpv's input. + if (!g_file_test(path, G_FILE_TEST_IS_REGULAR)) { + return NULL; + } + if (!avformat_open_input(&context, path, NULL, NULL)) { out = extract_embedded_art(context); avformat_close_input(&context); @@ -343,14 +377,9 @@ return NULL; } -// cached last file path, owned by mpv -static char *cached_path = NULL; - -// cached last artwork url, owned by glib -static gchar *cached_art_url = NULL; - -static void add_metadata_art(mpv_handle *mpv, GVariantDict *dict) +static void add_metadata_art(UserData *ud, GVariantDict *dict) { + mpv_handle *mpv = ud->mpv; char *path = mpv_get_property_string(mpv, "path"); if (!path) { @@ -358,17 +387,17 @@ } // mpv may call create_metadata multiple times, so cache to save CPU - if (!cached_path || strcmp(path, cached_path)) { - mpv_free(cached_path); - g_free(cached_art_url); - cached_path = path; - cached_art_url = get_art_url(mpv, path); - } else { - mpv_free(path); + if (!ud->cached_path || strcmp(path, ud->cached_path)) { + g_free(ud->cached_path); + g_free(ud->cached_art_url); + ud->cached_path = g_strdup(path); + ud->cached_art_url = get_art_url(mpv, path); } - if (cached_art_url) { - g_variant_dict_insert(dict, "mpris:artUrl", "s", cached_art_url); + mpv_free(path); + + if (ud->cached_art_url) { + g_variant_dict_insert(dict, "mpris:artUrl", "s", ud->cached_art_url); } } @@ -391,8 +420,8 @@ } if (g_date_valid(date)) { - gchar iso8601[20]; - g_date_strftime(iso8601, 20, "%Y-%m-%dT00:00:00Z", date); + gchar iso8601[21]; + g_date_strftime(iso8601, 21, "%Y-%m-%dT00:00:00Z", date); g_variant_dict_insert(dict, "xesam:contentCreated", "s", iso8601); } @@ -412,9 +441,9 @@ // mpris:trackid // playlist_pos < 0 if there is no playlist or current track if (ud->playlist_pos < 0) { - temp_str = g_strdup("/noplaylist"); + temp_str = g_strdup(NO_TRACK_ID); } else { - temp_str = g_strdup_printf("/%" PRId64, ud->playlist_pos); + temp_str = g_strdup_printf("%s%" PRId64, TRACK_PATH_PREFIX, ud->playlist_pos); } g_variant_dict_insert(&dict, "mpris:trackid", "o", temp_str); g_free(temp_str); @@ -457,7 +486,7 @@ add_metadata_item_int(ud->mpv, &dict, "metadata/by-key/Disc", "xesam:discNumber"); add_metadata_uri(ud->mpv, &dict); - add_metadata_art(ud->mpv, &dict); + add_metadata_art(ud, &dict); add_metadata_content_created(ud->mpv, &dict); return g_variant_dict_end(&dict); @@ -504,12 +533,12 @@ ret = g_variant_new_boolean(TRUE); } else if (g_strcmp0(property_name, "Fullscreen") == 0) { - int fullscreen; + int fullscreen = 0; mpv_get_property(ud->mpv, "fullscreen", MPV_FORMAT_FLAG, &fullscreen); ret = g_variant_new_boolean(fullscreen); } else if (g_strcmp0(property_name, "CanSetFullscreen") == 0) { - int can_fullscreen; + int can_fullscreen = 0; mpv_get_property(ud->mpv, "vo-configured", MPV_FORMAT_FLAG, &can_fullscreen); ret = g_variant_new_boolean(can_fullscreen); @@ -520,7 +549,9 @@ ret = g_variant_new_boolean(FALSE); } else if (g_strcmp0(property_name, "Identity") == 0) { - ret = g_variant_new_string("mpv"); + char *client_name = mpv_get_property_string(ud->mpv, "audio-client-name"); + ret = g_variant_new_string(client_name); + mpv_free(client_name); } else if (g_strcmp0(property_name, "DesktopEntry") == 0) { ret = g_variant_new_string("mpv"); @@ -600,13 +631,8 @@ g_dbus_method_invocation_return_value(invocation, NULL); } else if (g_strcmp0(method_name, "PlayPause") == 0) { - int paused; - if (ud->status == STATUS_PAUSED) { - paused = FALSE; - } else { - paused = TRUE; - } - mpv_set_property(ud->mpv, "pause", MPV_FORMAT_FLAG, &paused); + const char *cmd[] = {"cycle", "pause", NULL}; + mpv_command_async(ud->mpv, 0, cmd); g_dbus_method_invocation_return_value(invocation, NULL); } else if (g_strcmp0(method_name, "Play") == 0) { @@ -631,15 +657,14 @@ } else if (g_strcmp0(method_name, "Seek") == 0) { int64_t offset_us; // in microseconds - char *offset_str; + char offset_str[G_ASCII_DTOSTR_BUF_SIZE]; g_variant_get(parameters, "(x)", &offset_us); double offset_s = offset_us / 1000000.0; - offset_str = g_strdup_printf("%f", offset_s); + g_ascii_dtostr(offset_str, G_ASCII_DTOSTR_BUF_SIZE, offset_s); const char *cmd[] = {"seek", offset_str, NULL}; mpv_command_async(ud->mpv, 0, cmd); g_dbus_method_invocation_return_value(invocation, NULL); - g_free(offset_str); } else if (g_strcmp0(method_name, "SetPosition") == 0) { char *object_path; @@ -647,9 +672,11 @@ int64_t new_position_us; g_variant_get(parameters, "(&ox)", &object_path, &new_position_us); - new_position_s = ((float)new_position_us) / 1000000.0; // us -> s + new_position_s = ((double)new_position_us) / 1000000.0; // us -> s - if (ud->playlist_pos == g_ascii_strtoll(object_path + 1, NULL, 10)) { + if (g_str_has_prefix(object_path, TRACK_PATH_PREFIX) && + ud->playlist_pos == g_ascii_strtoll(object_path + strlen(TRACK_PATH_PREFIX), + NULL, 10)) { mpv_set_property(ud->mpv, "time-pos", MPV_FORMAT_DOUBLE, &new_position_s); } @@ -686,31 +713,31 @@ ret = g_variant_new_string(ud->loop_status); } else if (g_strcmp0(property_name, "Rate") == 0) { - double rate; + double rate = 1.0; mpv_get_property(ud->mpv, "speed", MPV_FORMAT_DOUBLE, &rate); ret = g_variant_new_double(rate); } else if (g_strcmp0(property_name, "Shuffle") == 0) { - int shuffle; + int shuffle = 0; mpv_get_property(ud->mpv, "shuffle", MPV_FORMAT_FLAG, &shuffle); ret = g_variant_new_boolean(shuffle); } else if (g_strcmp0(property_name, "Metadata") == 0) { if (!ud->metadata) { - ud->metadata = create_metadata(ud); + ud->metadata = g_variant_ref_sink(create_metadata(ud)); } // Increase reference count to prevent it from being freed after returning g_variant_ref(ud->metadata); ret = ud->metadata; } else if (g_strcmp0(property_name, "Volume") == 0) { - double volume; + double volume = 0; mpv_get_property(ud->mpv, "volume", MPV_FORMAT_DOUBLE, &volume); volume /= 100; ret = g_variant_new_double(volume); } else if (g_strcmp0(property_name, "Position") == 0) { - double position_s; + double position_s = 0; int64_t position_us; mpv_get_property(ud->mpv, "time-pos", MPV_FORMAT_DOUBLE, &position_s); position_us = position_s * 1000000.0; // s -> us @@ -729,10 +756,10 @@ ret = g_variant_new_boolean(can_go_previous(ud)); } else if (g_strcmp0(property_name, "CanPlay") == 0) { - ret = g_variant_new_boolean(TRUE); + ret = g_variant_new_boolean(can_play_pause(ud)); } else if (g_strcmp0(property_name, "CanPause") == 0) { - ret = g_variant_new_boolean(TRUE); + ret = g_variant_new_boolean(can_play_pause(ud)); } else if (g_strcmp0(property_name, "CanSeek") == 0) { ret = g_variant_new_boolean(TRUE); @@ -852,7 +879,7 @@ static void emit_seeked_signal(UserData *ud) { GVariant *params; - double position_s; + double position_s = 0; int64_t position_us; GError *error = NULL; mpv_get_property(ud->mpv, "time-pos", MPV_FORMAT_DOUBLE, &position_s); @@ -893,6 +920,11 @@ return ud->playlist_pos > 0; } +static gboolean can_play_pause(UserData *ud) +{ + return !ud->idle; +} + static GVariant * set_playback_status(UserData *ud) { if (ud->idle) { @@ -908,12 +940,17 @@ static void set_stopped_status(UserData *ud) { const char *prop_name = "PlaybackStatus"; - GVariant *prop_value = g_variant_new_string(STATUS_STOPPED); + GVariant *prop_value = g_variant_ref_sink(g_variant_new_string(STATUS_STOPPED)); + ud->idle = TRUE; ud->status = STATUS_STOPPED; g_hash_table_insert(ud->changed_properties, (gpointer)prop_name, prop_value); + g_hash_table_insert(ud->changed_properties, "CanPlay", + g_variant_ref_sink(g_variant_new_boolean(can_play_pause(ud)))); + g_hash_table_insert(ud->changed_properties, "CanPause", + g_variant_ref_sink(g_variant_new_boolean(can_play_pause(ud)))); emit_property_changes(ud); } @@ -925,7 +962,7 @@ { GError *error = NULL; UserData *ud = user_data; - ud->connection = connection; + g_set_object(&ud->connection, connection); if (ud->root_interface_id == 0) { ud->root_interface_id = @@ -952,8 +989,10 @@ } if (!ud->events_setup) { - setup_mpv_event_sources(ud); - ud->events_setup = TRUE; + ud->events_setup = setup_mpv_event_sources(ud); + if (!ud->events_setup) { + g_main_loop_quit(ud->loop); + } } } @@ -970,38 +1009,94 @@ return id; } -static char *build_bus_name(const char *client_name, gboolean unique_suffix) +static char *build_bus_name(const char *client_name) { GString *name = g_string_new("org.mpris.MediaPlayer2.mpv"); // don't append client name if it is the default value 'mpv' if (g_strcmp0(client_name, "mpv") != 0) { - g_string_append_printf(name, ".%s", client_name); - } + g_string_append_c(name, '.'); + if (!client_name || !*client_name) { + g_string_append_c(name, '_'); + } else { + for (const unsigned char *p = (const unsigned char *)client_name; *p; p++) { + if (g_ascii_isalpha(*p) || *p == '-' || + (g_ascii_isdigit(*p) && p != (const unsigned char *)client_name)) { + g_string_append_c(name, *p); + } else if (*p == ' ') { + g_string_append_c(name, '_'); + } else { + g_string_append_printf(name, "_%02X", *p); + } + } + } - if (unique_suffix) { - char *id = generate_random_id(); - g_string_append_printf(name, ".instance-%s", id); - g_free(id); + // Reserve 18 bytes for the instance suffix within D-Bus's 255-byte limit. + if (name->len > 237) { + gchar *hash = g_compute_checksum_for_string(G_CHECKSUM_SHA256, name->str, -1); + g_string_truncate(name, 172); + g_string_append_printf(name, "_%s", hash); + g_free(hash); + } } + char *id = generate_random_id(); + g_string_append_printf(name, ".instance-%s", id); + g_free(id); + return g_string_free(name, FALSE); } +static void bus_owner_destroyed(gpointer data) +{ + UserData *ud = data; + ud->pending_bus_owners--; +} + +static gboolean retry_bus_name(gpointer data) +{ + UserData *ud = data; + char *name = build_bus_name(ud->client_name); + guint previous_id = ud->bus_id; + ud->bus_id = 0; + g_bus_unown_name(previous_id); + ud->bus_id = g_bus_own_name(G_BUS_TYPE_SESSION, + name, + G_BUS_NAME_OWNER_FLAGS_NONE, + NULL, NULL, NULL, + ud, bus_owner_destroyed); + if (ud->bus_id != 0) { + ud->pending_bus_owners++; + } + g_free(name); + return G_SOURCE_REMOVE; +} + static void on_name_lost(GDBusConnection *connection, G_GNUC_UNUSED const char *_name, gpointer user_data) { UserData *ud = user_data; + // Stop the plugin if the initial connection to the session bus failed. + if (!connection && !ud->events_setup) { + g_main_loop_quit(ud->loop); + return; + } + if (connection) { - char *name = build_bus_name(ud->client_name, TRUE); + char *name = build_bus_name(ud->client_name); ud->bus_id = g_bus_own_name(G_BUS_TYPE_SESSION, name, G_BUS_NAME_OWNER_FLAGS_NONE, NULL, NULL, NULL, - &ud, NULL); + ud, NULL); g_free(name); + + // Let GLib finish processing the failed request before releasing it. + ud->bus_retry_source = g_idle_source_new(); + g_source_set_callback(ud->bus_retry_source, retry_bus_name, ud, NULL); + g_source_attach(ud->bus_retry_source, ud->ctx); } else { ud->root_interface_id = 0; ud->player_interface_id = 0; @@ -1013,6 +1108,7 @@ const char *prop_name = NULL; GVariant *prop_value = NULL; gboolean update_can_go_next_prev = FALSE; + gboolean update_can_play_pause = FALSE; if (g_strcmp0(name, "pause") == 0) { ud->paused = *(int*)data; @@ -1023,6 +1119,7 @@ ud->idle = *(int*)data; prop_name = "PlaybackStatus"; prop_value = set_playback_status(ud); + update_can_play_pause = TRUE; } else if (g_strcmp0(name, "media-title") == 0 || g_strcmp0(name, "duration") == 0) { @@ -1030,7 +1127,7 @@ if (ud->metadata) { g_variant_unref(ud->metadata); } - ud->metadata = create_metadata(ud); + ud->metadata = g_variant_ref_sink(create_metadata(ud)); prop_name = "Metadata"; prop_value = ud->metadata; @@ -1050,7 +1147,7 @@ if (g_strcmp0(status, "no") != 0) { ud->loop_status = LOOP_TRACK; } else { - char *playlist_status; + char *playlist_status = NULL; mpv_get_property(ud->mpv, "loop-playlist", MPV_FORMAT_STRING, &playlist_status); if (g_strcmp0(playlist_status, "no") != 0) { ud->loop_status = LOOP_PLAYLIST; @@ -1068,7 +1165,7 @@ if (g_strcmp0(status, "no") != 0) { ud->loop_status = LOOP_PLAYLIST; } else { - char *file_status; + char *file_status = NULL; mpv_get_property(ud->mpv, "loop-file", MPV_FORMAT_STRING, &file_status); if (g_strcmp0(file_status, "no") != 0) { ud->loop_status = LOOP_TRACK; @@ -1103,7 +1200,7 @@ if (prop_name) { if (prop_value) { - g_variant_ref(prop_value); + g_variant_ref_sink(prop_value); } g_hash_table_insert(ud->changed_properties, (gpointer)prop_name, prop_value); @@ -1111,9 +1208,16 @@ if (update_can_go_next_prev) { g_hash_table_insert(ud->changed_properties, "CanGoNext", - g_variant_new_boolean(can_go_next(ud))); + g_variant_ref_sink(g_variant_new_boolean(can_go_next(ud)))); g_hash_table_insert(ud->changed_properties, "CanGoPrevious", - g_variant_new_boolean(can_go_previous(ud))); + g_variant_ref_sink(g_variant_new_boolean(can_go_previous(ud)))); + } + + if (update_can_play_pause) { + g_hash_table_insert(ud->changed_properties, "CanPlay", + g_variant_ref_sink(g_variant_new_boolean(can_play_pause(ud)))); + g_hash_table_insert(ud->changed_properties, "CanPause", + g_variant_ref_sink(g_variant_new_boolean(can_play_pause(ud)))); } } @@ -1162,35 +1266,37 @@ (void)!write(*((int*)fd), "0", 1); } -static void setup_mpv_event_sources(UserData *ud) +static gboolean setup_mpv_event_sources(UserData *ud) { GError *error = NULL; - GSource *mpv_pipe_source; - GSource *timeout_source; - g_unix_open_pipe(ud->wakeup_pipe, 0, &error); - if (error != NULL) { - g_printerr("%s", error->message); + if (!g_unix_open_pipe(ud->wakeup_pipe, FD_CLOEXEC, &error)) { + g_printerr("%s\n", error->message); g_clear_error(&error); + return FALSE; + } + if (!g_unix_set_fd_nonblocking(ud->wakeup_pipe[0], TRUE, &error) || + !g_unix_set_fd_nonblocking(ud->wakeup_pipe[1], TRUE, &error)) { + g_printerr("%s\n", error->message); + g_clear_error(&error); + return FALSE; } - fcntl(ud->wakeup_pipe[0], F_SETFL, O_NONBLOCK); mpv_set_wakeup_callback(ud->mpv, wakeup_handler, &ud->wakeup_pipe[1]); - mpv_pipe_source = g_unix_fd_source_new(ud->wakeup_pipe[0], G_IO_IN); - g_source_set_callback(mpv_pipe_source, + ud->mpv_pipe_source = g_unix_fd_source_new(ud->wakeup_pipe[0], G_IO_IN); + g_source_set_callback(ud->mpv_pipe_source, G_SOURCE_FUNC(event_handler), ud, NULL); - g_source_attach(mpv_pipe_source, ud->ctx); - g_source_unref(mpv_pipe_source); + g_source_attach(ud->mpv_pipe_source, ud->ctx); - timeout_source = g_timeout_source_new(100); - g_source_set_callback(timeout_source, + ud->timeout_source = g_timeout_source_new(100); + g_source_set_callback(ud->timeout_source, G_SOURCE_FUNC(emit_property_changes), ud, NULL); - g_source_attach(timeout_source, ud->ctx); - g_source_unref(timeout_source); + g_source_attach(ud->timeout_source, ud->ctx); + return TRUE; } // Plugin entry point @@ -1198,7 +1304,7 @@ { GMainContext *ctx; GMainLoop *loop; - UserData ud = {0}; + UserData ud = {.wakeup_pipe = {-1, -1}}; GError *error = NULL; GDBusNodeInfo *introspection_data = NULL; @@ -1221,25 +1327,33 @@ ud.ctx = ctx; ud.status = STATUS_STOPPED; ud.loop_status = LOOP_NONE; - ud.changed_properties = g_hash_table_new(g_str_hash, g_str_equal); + ud.changed_properties = g_hash_table_new_full(g_str_hash, g_str_equal, + NULL, + (GDestroyNotify)g_variant_unref); ud.seek_expected = FALSE; ud.idle = FALSE; ud.paused = FALSE; ud.shuffle = FALSE; - ud.client_name = mpv_get_property_string(mpv, "audio-client-name"); + char *client_name = mpv_get_property_string(mpv, "audio-client-name"); + ud.client_name = g_strdup(client_name); + mpv_free(client_name); mpv_get_property(mpv, "playlist-count", MPV_FORMAT_INT64, &ud.playlist_count); mpv_get_property(mpv, "playlist-pos", MPV_FORMAT_INT64, &ud.playlist_pos); - char *bus_name = build_bus_name(ud.client_name, FALSE); + char *bus_name = build_bus_name(ud.client_name); g_main_context_push_thread_default(ctx); - ud.bus_id = g_bus_own_name(G_BUS_TYPE_SESSION, + if (g_dbus_is_name(bus_name)) { + ud.bus_id = g_bus_own_name(G_BUS_TYPE_SESSION, bus_name, G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, on_bus_acquired, NULL, on_name_lost, - &ud, NULL); - g_main_context_pop_thread_default(ctx); + &ud, bus_owner_destroyed); + if (ud.bus_id != 0) { + ud.pending_bus_owners++; + } + } g_free(bus_name); // Receive event for property changes @@ -1256,16 +1370,58 @@ mpv_observe_property(mpv, 0, "playlist-count", MPV_FORMAT_INT64); mpv_observe_property(mpv, 0, "playlist-pos", MPV_FORMAT_INT64); - g_main_loop_run(loop); + if (ud.bus_id != 0) { + g_main_loop_run(loop); + } else { + g_printerr("Failed to register MPRIS bus name\n"); + } - g_dbus_connection_unregister_object(ud.connection, ud.root_interface_id); - g_dbus_connection_unregister_object(ud.connection, ud.player_interface_id); + if (ud.bus_retry_source) { + g_source_destroy(ud.bus_retry_source); + g_source_unref(ud.bus_retry_source); + } + mpv_set_wakeup_callback(mpv, NULL, NULL); + if (ud.mpv_pipe_source) { + g_source_destroy(ud.mpv_pipe_source); + g_source_unref(ud.mpv_pipe_source); + } + if (ud.timeout_source) { + g_source_destroy(ud.timeout_source); + g_source_unref(ud.timeout_source); + } + for (int i = 0; i < 2; i++) { + if (ud.wakeup_pipe[i] >= 0) { + close(ud.wakeup_pipe[i]); + } + } - g_bus_unown_name(ud.bus_id); + if (ud.connection) { + g_dbus_connection_unregister_object(ud.connection, ud.root_interface_id); + g_dbus_connection_unregister_object(ud.connection, ud.player_interface_id); + } + + if (ud.bus_id != 0) { + g_bus_unown_name(ud.bus_id); + } + // Unowning is asynchronous; keep callback data alive until all owners finish. + while (ud.pending_bus_owners != 0) { + g_main_context_iteration(ctx, TRUE); + } + + if (ud.metadata) { + g_variant_unref(ud.metadata); + } + g_hash_table_unref(ud.changed_properties); + + g_clear_object(&ud.connection); + g_main_context_pop_thread_default(ctx); g_main_loop_unref(loop); g_main_context_unref(ctx); g_dbus_node_info_unref(introspection_data); - mpv_free(ud.client_name); + + g_free(ud.client_name); + g_free(ud.cached_path); + g_free(ud.cached_art_url); return 0; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpv-mpris-1.2/test/env new/mpv-mpris-1.3/test/env --- old/mpv-mpris-1.2/test/env 2026-01-01 23:21:19.000000000 +0100 +++ new/mpv-mpris-1.3/test/env 2026-09-17 01:23:53.000000000 +0200 @@ -41,9 +41,8 @@ if [ -n "$MPV_MPRIS_TEST_PLUGIN" ] ; then if [ ! -f "$MPV_MPRIS_TEST_PLUGIN" ] || - [ ! -r "$MPV_MPRIS_TEST_PLUGIN" ] || - [ ! -x "$MPV_MPRIS_TEST_PLUGIN" ] ; then - echo "$MPV_MPRIS_TEST_PLUGIN not an existing file with rx perms" >&2 + [ ! -r "$MPV_MPRIS_TEST_PLUGIN" ] ; then + echo "$MPV_MPRIS_TEST_PLUGIN not an existing file with read perms" >&2 exit 1 fi fi diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/mpv-mpris-1.2/test/setup new/mpv-mpris-1.3/test/setup --- old/mpv-mpris-1.2/test/setup 2026-01-01 23:21:19.000000000 +0100 +++ new/mpv-mpris-1.3/test/setup 2026-09-17 01:23:53.000000000 +0200 @@ -64,7 +64,7 @@ playerctl_list_all_is_mpv () { ret=0 ; player="$(playerctl --list-all 2> /dev/null)" || ret=$? - if [ $ret -ne 0 ] || [ "$player" != mpv ] ; then + if [ $ret -ne 0 ] || [[ ! "$player" =~ ^mpv\.instance-[A-Za-z]{8}$ ]] ; then playerctl --list-all ; return $((ret?ret:1)) fi } @@ -77,7 +77,7 @@ mpris_quit () { # playerctl does not yet support the MPRIS Quit API # https://github.com/altdesktop/playerctl/issues/171 - dbus-send --print-reply --dest=org.mpris.MediaPlayer2.mpv /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Quit + dbus-send --print-reply --dest="org.mpris.MediaPlayer2.$player" /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Quit }
