Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package playerctl for openSUSE:Factory checked in at 2021-09-28 19:16:48 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/playerctl (Old) and /work/SRC/openSUSE:Factory/.playerctl.new.1899 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "playerctl" Tue Sep 28 19:16:48 2021 rev:4 rq:922013 version:2.4.1 Changes: -------- --- /work/SRC/openSUSE:Factory/playerctl/playerctl.changes 2020-12-02 13:58:03.453790781 +0100 +++ /work/SRC/openSUSE:Factory/.playerctl.new.1899/playerctl.changes 2021-09-28 19:17:46.852272137 +0200 @@ -1,0 +2,13 @@ +Sat Sep 25 16:21:35 UTC 2021 - Luigi Baldoni <[email protected]> + +- Update to version 2.4.1 + * Fix a crash in playerctld when players use TrackList and + Playlists interfaces + * Add the trunc() template function + * Allow to use playerctl as a subproject and cpp linking + * bugfix: subscribe to all signals when multiple template + functions are used + * bugfix: workaround for players that use uint64 values in the + formatter + +------------------------------------------------------------------- Old: ---- playerctl-2.3.1.tar.gz New: ---- playerctl-2.4.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ playerctl.spec ++++++ --- /var/tmp/diff_new_pack.XgO69N/_old 2021-09-28 19:17:47.300272653 +0200 +++ /var/tmp/diff_new_pack.XgO69N/_new 2021-09-28 19:17:47.304272657 +0200 @@ -1,7 +1,7 @@ # # spec file for package playerctl # -# Copyright (c) 2020 SUSE LLC +# Copyright (c) 2021 SUSE LLC # Copyright (c) 2017 Dakota Williams <[email protected]> # # All modifications and additions to the file contributed by third parties @@ -23,14 +23,14 @@ %global libname lib%{name}%{sover} %global typelibname typelib-1_0-Playerctl-%{majorver}_%{minorver} Name: playerctl -Version: 2.3.1 +Version: 2.4.1 Release: 0 Summary: MPRIS command-line controller and library for media players License: LGPL-3.0-or-later Group: Productivity/Multimedia/Other URL: https://github.com/acrisci/playerctl Source0: https://github.com/acrisci/playerctl/archive/v%{version}/%{name}-%{version}.tar.gz -BuildRequires: meson >= 0.50.0 +BuildRequires: meson >= 0.56.0 BuildRequires: pkgconfig BuildRequires: pkgconfig(dbus-1) BuildRequires: pkgconfig(gio-unix-2.0) ++++++ playerctl-2.3.1.tar.gz -> playerctl-2.4.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/.gitignore new/playerctl-2.4.1/.gitignore --- old/playerctl-2.3.1/.gitignore 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/.gitignore 2021-09-21 14:23:38.000000000 +0200 @@ -3,8 +3,8 @@ /build /mesonbuild /playerctl-fpm -.swp -.swo -.swn -.snap -env +*.swp +*.swo +*.swn +*.snap +/env diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/CHANGELOG.md new/playerctl-2.4.1/CHANGELOG.md --- old/playerctl-2.3.1/CHANGELOG.md 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/CHANGELOG.md 2021-09-21 14:23:38.000000000 +0200 @@ -1,5 +1,15 @@ # Changelog +## Version 2.4.1 + +Version 2.4.1 contains bugfixes and new features. + +* Fix a crash in playerctld when players use TrackList and Playlists interfaces (#215) +* Add the `trunc()` template function (#224) +* Allow to use playerctl as a subproject and cpp linking (#228) +* bugfix: subscribe to all signals when multiple template functions are used (#235) +* bugfix: workaround for players that use uint64 values in the formatter (#234) + ## Version 2.3.1 Version 2.3.1 contains bugfixes and new features. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/README.md new/playerctl-2.4.1/README.md --- old/playerctl-2.3.1/README.md 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/README.md 2021-09-21 14:23:38.000000000 +0200 @@ -116,6 +116,7 @@ | `markup_escape` | string | Escape XML markup characters in the string. | | `default` | any, any | Print the first value if it is present, or else print the second. | | `emoji` | status or volume | Try to convert the variable to an emoji representation. | +| `trunc` | string, int | Truncate string to a maximum length. | | Variable | Description | | ------------ | ------------------------------------------------- | @@ -135,6 +136,21 @@ playerctl metadata --format '{{ playerName }}: {{ artist }} - {{ title }} {{ duration(position) }}|{{ duration(mpris:length) }}' --follow ``` +### Changing the position of the track + +You can seek to a position in the track or skip forward and back. + +```bash +# Go back 30 seconds +playerctl position 30- + +# Go forward 30 seconds +playerctl position 30+ + +# Seek to the position at 30 seconds +playerctl position 30 +``` + ## Troubleshooting ### Debug Logging @@ -143,15 +159,16 @@ ### No Players Found -If you are using Quod Libet as your music player you need to install/activate a plugin for it. -In Quod Libet open the window File -> Plugins and select the plugin called *MPRIS D-Bus Support*. - Some players like Spotify require certain DBus environment variables to be set which are normally set within the session manager. If you're not using a session manager or it does not set these variables automatically (like `xinit`), launch your desktop environment wrapped in a `dbus-launch` command. For example, in your `.xinitrc` file, use this to start your WM: ``` exec dbus-launch --autolaunch=$(cat /var/lib/dbus/machine-id) i3 ``` +Some players may require installation of a plugin or other configuration. + +In Quod Libet open the window File -> Plugins and select the plugin called *MPRIS D-Bus Support*. + ### Playerctld Autostart Issues If `playerctld` does not autostart and you use `xinit` and systemd, you might need this fix to enable DBus activation to work correctly: @@ -207,7 +224,7 @@ sudo ninja -C mesonbuild install ``` -Note that you need `meson >= 0.50.0` installed. In case your distro only has an older version of meson in its repository you can install the newest version via pip: +Note that you need `meson` installed. In case your distro only has an older version of meson in its repository you can install the newest version via pip: ``` pip3 install meson diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/data/mpris-dbus-interface.xml new/playerctl-2.4.1/data/mpris-dbus-interface.xml --- old/playerctl-2.3.1/data/mpris-dbus-interface.xml 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/data/mpris-dbus-interface.xml 2021-09-21 14:23:38.000000000 +0200 @@ -50,7 +50,90 @@ <property access="read" type="b" name="CanSeek"/> <property access="read" type="b" name="CanControl"/> </interface> - <interface name="org.freedesktop.DBus.Properties"> + <interface name="org.mpris.MediaPlayer2.TrackList"> + <method name="GetTracksMetadata"> + <arg direction="in" name="TrackIds" type="ao"> + </arg> + <arg direction="out" type="aa{sv}" name="Metadata"> + </arg> + </method> + <method name="AddTrack"> + <arg direction="in" type="s" name="Uri"> + </arg> + <arg direction="in" type="o" name="AfterTrack"> + </arg> + <arg direction="in" type="b" name="SetAsCurrent"> + </arg> + </method> + <method name="RemoveTrack"> + <arg direction="in" type="o" name="TrackId"> + </arg> + </method> + <method name="GoTo"> + <arg direction="in" type="o" name="TrackId"> + </arg> + </method> + <property name="Tracks" type="ao" access="read"> + <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="invalidates"/> + </property> + <property name="CanEditTracks" type="b" access="read"> + <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/> + </property> + <signal name="TrackListReplaced"> + <arg name="Tracks" type="ao"> + </arg> + <arg name="CurrentTrack" type="o"> + </arg> + </signal> + <signal name="TrackAdded"> + <arg type="a{sv}" name="Metadata"> + </arg> + <arg type="o" name="AfterTrack"> + </arg> + </signal> + <signal name="TrackRemoved"> + <arg type="o" name="TrackId"> + </arg> + </signal> + <signal name="TrackMetadataChanged"> + <arg type="o" name="TrackId"> + </arg> + <arg type="a{sv}" name="Metadata"> + </arg> + </signal> + </interface> + <interface name="org.mpris.MediaPlayer2.Playlists"> + <method name="ActivatePlaylist"> + <arg direction="in" name="PlaylistId" type="o"> + </arg> + </method> + <method name="GetPlaylists"> + <arg direction="in" name="Index" type="u"> + </arg> + <arg direction="in" name="MaxCount" type="u"> + </arg> + <arg direction="in" name="Order" type="s"> + </arg> + <arg direction="in" name="ReverseOrder" type="b"> + </arg> + <arg direction="out" name="Playlists" type="a(oss)"> + </arg> + </method> + <property name="PlaylistCount" type="u" access="read"> + <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/> + </property> + <property name="Orderings" type="as" access="read"> + <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/> + </property> + <property name="ActivePlaylist" type="(b(oss))" access="read"> + <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/> + </property> + <signal name="PlaylistChanged"> + <arg name="Playlist" type="(oss)"> + </arg> + </signal> + </interface> + <interface name="org.freedesktop.DBus.Properties"> <method name="Get"> <arg name="interface_name" type="s" direction="in"/> <arg name="property_name" type="s" direction="in"/> @@ -78,4 +161,5 @@ <arg name="machine_uuid" type="s" direction="out"/> </method> </interface> + </node> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/data/playerctl.zsh new/playerctl-2.4.1/data/playerctl.zsh --- old/playerctl-2.3.1/data/playerctl.zsh 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/data/playerctl.zsh 2021-09-21 14:23:38.000000000 +0200 @@ -43,7 +43,7 @@ 'stop:Command the player to stop' \ 'next:Command the player to skip to the next track' \ 'previous:Command the player to skip to the previous track' \ - 'position:Command the palyer to go or seek to the position' \ + 'position:Command the player to go or seek to the position' \ 'volume:Print or set the volume level from 0.0 to 1.0' \ 'status:Get the play status of the player' \ 'metadata:Print the metadata information for the current track:$playerctl_command_metadata_keys' \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/doc/playerctl.1.in new/playerctl-2.4.1/doc/playerctl.1.in --- old/playerctl-2.3.1/doc/playerctl.1.in 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/doc/playerctl.1.in 2021-09-21 14:23:38.000000000 +0200 @@ -251,6 +251,12 @@ .Fa status and .Fa volume . +.It Fn trunc str len +Truncate +.Fa str +to a maximum of +.Fa len +characters, adding an ellipsis (???) if necessary. .El .Pp The template language is also able to perform basic math operations. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/meson.build new/playerctl-2.4.1/meson.build --- old/playerctl-2.3.1/meson.build 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/meson.build 2021-09-21 14:23:38.000000000 +0200 @@ -1,11 +1,11 @@ project( 'playerctl', 'c', - version: '2.3.1', - meson_version: '>=0.50.0' + version: '2.4.1', + meson_version: '>=0.56.0' ) -release_date = 'November 30, 2020' +release_date = 'September 21, 2021' gnome = import('gnome') pkgconfig = import('pkgconfig') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/playerctl/meson.build new/playerctl-2.4.1/playerctl/meson.build --- old/playerctl-2.3.1/playerctl/meson.build 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/playerctl/meson.build 2021-09-21 14:23:38.000000000 +0200 @@ -9,7 +9,7 @@ playerctl_generated = gnome.gdbus_codegen( 'playerctl-generated', - join_paths(meson.source_root(), 'data', 'mpris-dbus-interface.xml'), + join_paths(meson.project_source_root(), 'data', 'mpris-dbus-interface.xml'), ) headers = [ @@ -30,11 +30,10 @@ ] # Allow including playerctl.h during compilation -add_global_arguments( +c_args = [ '-DPLAYERCTL_COMPILATION', '-DG_LOG_DOMAIN="playerctl"', - language: 'c', -) +] enums = gnome.mkenums_simple( 'playerctl-enum-types', @@ -48,10 +47,13 @@ gio_dep, ] -symbols_file = join_paths(meson.source_root(), 'data', 'playerctl.syms') +symbols_file = join_paths(meson.project_source_root(), 'data', 'playerctl.syms') symbols_flag = '-Wl,--version-script,@0@'.format(symbols_file) -playerctl_lib = both_libraries( +# default_library is shared by default see +# https://mesonbuild.com/Builtin-options.html this enabled the project +# to be either statically or dynamically linked as a subproject +playerctl_lib = library( 'playerctl', playerctl_sources, enums, dependencies: deps, @@ -60,12 +62,14 @@ install: true, link_args : symbols_flag, link_depends: symbols_file, + c_args: c_args, ) # Required for linking against the shared lib we just built playerctl_shared_link = declare_dependency( - link_with: playerctl_lib.get_shared_lib(), + link_with: playerctl_lib, dependencies: deps, + include_directories: include_directories('..'), ) playerctl_executable = executable( @@ -82,6 +86,7 @@ dependencies: deps, include_directories: configuration_inc, install: true, + c_args: c_args, ) install_headers( @@ -99,7 +104,7 @@ endif gnome.generate_gir( - playerctl_lib.get_shared_lib(), + playerctl_lib, sources: [ enums, 'playerctl-player-name.c', @@ -118,7 +123,7 @@ endif pkgconfig.generate( - libraries: playerctl_lib.get_shared_lib(), + libraries: playerctl_lib, subdirs: 'playerctl', version: meson.project_version(), name: 'Playerctl', diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/playerctl/playerctl-cli.c new/playerctl-2.4.1/playerctl/playerctl-cli.c --- old/playerctl-2.3.1/playerctl/playerctl-cli.c 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/playerctl/playerctl-cli.c 2021-09-21 14:23:38.000000000 +0200 @@ -228,7 +228,7 @@ GError *tmp_error = NULL; gchar *instance = pctl_player_get_instance(player); - // XXX there is no CanStop propery on the mpris player. CanPlay is supposed + // XXX there is no CanStop property on the mpris player. CanPlay is supposed // to indicate whether there is a current track. If there is no current // track, then I assume the player cannot stop. gboolean can_play = FALSE; @@ -936,7 +936,7 @@ } if (!one_selected && !no_status_error_messages) { - g_printerr("No players were found\n"); + g_printerr("No players found\n"); } pctl_player_name_list_destroy(player_names_list); @@ -1103,6 +1103,7 @@ if (any_index == INT_MAX) { any_index = i; } + ++i; continue; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/playerctl/playerctl-daemon.c new/playerctl-2.4.1/playerctl/playerctl-daemon.c --- old/playerctl-2.3.1/playerctl/playerctl-daemon.c 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/playerctl/playerctl-daemon.c 2021-09-21 14:23:38.000000000 +0200 @@ -9,15 +9,29 @@ #define DBUS_PATH "/org/freedesktop/DBus" #define PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" #define ROOT_INTERFACE "org.mpris.MediaPlayer2" +#define PLAYLISTS_INTERFACE "org.mpris.MediaPlayer2.Playlists" +#define TRACKLIST_INTERFACE "org.mpris.MediaPlayer2.TrackList" #define PROPERTIES_INTERFACE "org.freedesktop.DBus.Properties" #define PLAYERCTLD_INTERFACE "com.github.altdesktop.playerctld" +/** + * A representation of an MPRIS player and its cached MPRIS properties + */ struct Player { char *unique; char *well_known; gint64 position; GVariant *player_properties; GVariant *root_properties; + // org.mpris.MediaPlayer2.TrackList and org.mpris.MediaPlayer2.Playlists are optional + struct { + bool supported; + GVariant *properties; + } tracklist; + struct { + bool supported; + GVariant *properties; + } playlists; }; struct PlayerctldContext { @@ -27,6 +41,8 @@ GDBusConnection *connection; GDBusInterfaceInfo *root_interface_info; GDBusInterfaceInfo *player_interface_info; + GDBusInterfaceInfo *playlists_interface_info; + GDBusInterfaceInfo *tracklist_interface_info; GDBusInterfaceInfo *playerctld_interface_info; GQueue *players; GQueue *pending_players; @@ -34,10 +50,21 @@ struct Player *pending_active; }; +/** + * Allocate and create a new player, with the specified connection name and well-known bus name + */ static struct Player *player_new(const char *unique, const char *well_known) { struct Player *player = calloc(1, sizeof(struct Player)); player->unique = g_strdup(unique); player->well_known = g_strdup(well_known); + // Explicitly initialize everything else - just in case + player->position = 0; + player->player_properties = NULL; + player->root_properties = NULL; + player->tracklist.supported = false; + player->tracklist.properties = NULL; + player->playlists.supported = false; + player->playlists.properties = NULL; return player; } @@ -56,6 +83,12 @@ if (name->root_properties != NULL) { g_variant_unref(name->root_properties); } + if (name->tracklist.properties != NULL) { + g_variant_unref(name->tracklist.properties); + } + if (name->playlists.properties != NULL) { + g_variant_unref(name->playlists.properties); + } g_free(name->unique); g_free(name->well_known); free(name); @@ -85,12 +118,31 @@ GVariantDict cached_properties; GVariantIter iter; GVariant *child; - bool is_player_interface = false; // otherwise, the root interface + enum MprisInterface { PLAYER, TRACKLIST, PLAYLISTS, ROOT } interface; if (g_strcmp0(interface_name, PLAYER_INTERFACE) == 0) { + interface = PLAYER; g_variant_dict_init(&cached_properties, player->player_properties); - is_player_interface = true; + } else if (g_strcmp0(interface_name, TRACKLIST_INTERFACE) == 0) { + interface = TRACKLIST; + // FIXME: new value of Tracks property is not sent in PropertiesChanged signal + // We may want to listen for TrackAdded, TrackRemoved and TrackListReplaced + if (!player->playlists.supported) { + g_warning("Player %s doesn't appear to support interface %s, but sent " + "PropertiesChanged regarding its properties.", + player->well_known, interface_name); + } + g_variant_dict_init(&cached_properties, player->tracklist.properties); + } else if (g_strcmp0(interface_name, PLAYLISTS_INTERFACE) == 0) { + interface = PLAYLISTS; + if (!player->playlists.supported) { + g_warning("Player %s doesn't appear to support interface %s, but sent " + "PropertiesChanged regarding its properties.", + player->well_known, interface_name); + } + g_variant_dict_init(&cached_properties, player->playlists.properties); } else if (g_strcmp0(interface_name, ROOT_INTERFACE) == 0) { + interface = ROOT; g_variant_dict_init(&cached_properties, player->root_properties); } else { g_error("cannot update properties for unknown interface: %s", interface_name); @@ -102,13 +154,14 @@ assert(false); } g_variant_iter_init(&iter, properties); + while ((child = g_variant_iter_next_value(&iter))) { GVariant *key_variant = g_variant_get_child_value(child, 0); const gchar *key = g_variant_get_string(key_variant, 0); GVariant *prop_variant = g_variant_get_child_value(child, 1); GVariant *prop_value = g_variant_get_variant(prop_variant); // g_debug("key=%s, value=%s", key, g_variant_print(prop_value, TRUE)); - if (is_player_interface && g_strcmp0(key, "Position") == 0) { + if (interface == PLAYER && g_strcmp0(key, "Position") == 0) { // gets cached separately (never counts as changed) player->position = g_variant_get_int64(prop_value); goto loop_out; @@ -133,16 +186,31 @@ g_variant_unref(child); } - if (is_player_interface) { + switch (interface) { + case PLAYER: if (player->player_properties != NULL) { g_variant_unref(player->player_properties); } player->player_properties = g_variant_ref_sink(g_variant_dict_end(&cached_properties)); - } else { + break; + case TRACKLIST: + if (player->tracklist.properties != NULL) { + g_variant_unref(player->tracklist.properties); + } + player->tracklist.properties = g_variant_ref_sink(g_variant_dict_end(&cached_properties)); + break; + case PLAYLISTS: + if (player->playlists.properties != NULL) { + g_variant_unref(player->playlists.properties); + } + player->playlists.properties = g_variant_ref_sink(g_variant_dict_end(&cached_properties)); + break; + case ROOT: if (player->root_properties != NULL) { g_variant_unref(player->root_properties); } player->root_properties = g_variant_ref_sink(g_variant_dict_end(&cached_properties)); + break; } return changed; @@ -215,7 +283,7 @@ } GVariant *root_children[3] = { - g_variant_new_string(PLAYER_INTERFACE), + g_variant_new_string(ROOT_INTERFACE), player->root_properties, g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0), }; @@ -226,6 +294,42 @@ g_propagate_error(error, tmp_error); return; } + + // Emit nothing for unsupported optional interfaces + if (player->tracklist.supported) { + GVariant *tracklist_children[3] = { + g_variant_new_string(TRACKLIST_INTERFACE), + player->tracklist.properties, + g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0), + }; + GVariant *tracklist_properties_tuple = g_variant_new_tuple(tracklist_children, 3); + + g_dbus_connection_emit_signal(ctx->connection, NULL, MPRIS_PATH, PROPERTIES_INTERFACE, + "PropertiesChanged", tracklist_properties_tuple, + &tmp_error); + if (tmp_error != NULL) { + g_propagate_error(error, tmp_error); + return; + } + } + + if (player->playlists.supported) { + GVariant *playlists_children[3] = { + g_variant_new_string(PLAYLISTS_INTERFACE), + player->playlists.properties, + g_variant_new_array(G_VARIANT_TYPE_STRING, NULL, 0), + }; + GVariant *playlists_properties_tuple = g_variant_new_tuple(playlists_children, 3); + + g_dbus_connection_emit_signal(ctx->connection, NULL, MPRIS_PATH, PROPERTIES_INTERFACE, + "PropertiesChanged", playlists_properties_tuple, + &tmp_error); + if (tmp_error != NULL) { + g_propagate_error(error, tmp_error); + return; + } + } + g_debug("sending Seeked signal with position %ld", player->position); g_dbus_connection_emit_signal(ctx->connection, NULL, MPRIS_PATH, PLAYER_INTERFACE, "Seeked", g_variant_new("(x)", player->position), &tmp_error); @@ -272,6 +376,41 @@ g_propagate_error(error, tmp_error); return; } + + // Assume old player supported all optional interfaces and invalidate those properties + // unconditionally + const gchar *const tracklist_properties[] = {"Tracks", "CanEditTracks"}; + GVariant *tracklist_invalidated = g_variant_new_strv( + tracklist_properties, sizeof(tracklist_properties) / sizeof(tracklist_properties[0])); + GVariant *tracklist_children[3] = { + g_variant_new_string(ROOT_INTERFACE), + g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0), + tracklist_invalidated, + }; + GVariant *tracklist_invalidated_tuple = g_variant_new_tuple(tracklist_children, 3); + g_dbus_connection_emit_signal(ctx->connection, NULL, MPRIS_PATH, PROPERTIES_INTERFACE, + "PropertiesChanged", tracklist_invalidated_tuple, &tmp_error); + if (tmp_error != NULL) { + g_propagate_error(error, tmp_error); + return; + } + + const gchar *const playlists_properties[] = {"PlaylistCount", "Orderings", + "ActivePlaylist"}; + GVariant *playlists_invalidated = g_variant_new_strv( + playlists_properties, sizeof(playlists_properties) / sizeof(playlists_properties[0])); + GVariant *playlists_children[3] = { + g_variant_new_string(ROOT_INTERFACE), + g_variant_new_array(G_VARIANT_TYPE("{sv}"), NULL, 0), + playlists_invalidated, + }; + GVariant *playlists_invalidated_tuple = g_variant_new_tuple(playlists_children, 3); + g_dbus_connection_emit_signal(ctx->connection, NULL, MPRIS_PATH, PROPERTIES_INTERFACE, + "PropertiesChanged", playlists_invalidated_tuple, &tmp_error); + if (tmp_error != NULL) { + g_propagate_error(error, tmp_error); + return; + } } GVariantDict dict; @@ -495,6 +634,90 @@ " <property name=\"CanSeek\" type=\"b\" access=\"read\"/>\n" " <property name=\"CanControl\" type=\"b\" access=\"read\"/>\n" " </interface>\n" + " <interface name=\"org.mpris.MediaPlayer2.TrackList\">\n" + " <method name=\"GetTracksMetadata\">\n" + " <arg direction=\"in\" name=\"TrackIds\" type=\"ao\">\n" + " </arg>\n" + " <arg direction=\"out\" type=\"aa{sv}\" name=\"Metadata\">\n" + " </arg>\n" + " </method>\n" + " <method name=\"AddTrack\">\n" + " <arg direction=\"in\" type=\"s\" name=\"Uri\">\n" + " </arg>\n" + " <arg direction=\"in\" type=\"o\" name=\"AfterTrack\">\n" + " </arg>\n" + " <arg direction=\"in\" type=\"b\" name=\"SetAsCurrent\">\n" + " </arg>\n" + " </method>\n" + " <method name=\"RemoveTrack\">\n" + " <arg direction=\"in\" type=\"o\" name=\"TrackId\">\n" + " </arg>\n" + " </method>\n" + " <method name=\"GoTo\">\n" + " <arg direction=\"in\" type=\"o\" name=\"TrackId\">\n" + " </arg>\n" + " </method>\n" + " <property name=\"Tracks\" type=\"ao\" access=\"read\">\n" + " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" " + "value=\"invalidates\"/>\n" + " </property>\n" + " <property name=\"CanEditTracks\" type=\"b\" access=\"read\">\n" + " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>\n" + " </property>\n" + " <signal name=\"TrackListReplaced\">\n" + " <arg name=\"Tracks\" type=\"ao\">\n" + " </arg>\n" + " <arg name=\"CurrentTrack\" type=\"o\">\n" + " </arg>\n" + " </signal>\n" + " <signal name=\"TrackAdded\">\n" + " <arg type=\"a{sv}\" name=\"Metadata\">\n" + " </arg>\n" + " <arg type=\"o\" name=\"AfterTrack\">\n" + " </arg>\n" + " </signal>\n" + " <signal name=\"TrackRemoved\">\n" + " <arg type=\"o\" name=\"TrackId\">\n" + " </arg>\n" + " </signal>\n" + " <signal name=\"TrackMetadataChanged\">\n" + " <arg type=\"o\" name=\"TrackId\">\n" + " </arg>\n" + " <arg type=\"a{sv}\" name=\"Metadata\">\n" + " </arg>\n" + " </signal>\n" + " </interface>\n" + " <interface name=\"org.mpris.MediaPlayer2.Playlists\">\n" + " <method name=\"ActivatePlaylist\">\n" + " <arg direction=\"in\" name=\"PlaylistId\" type=\"o\">\n" + " </arg>\n" + " </method>\n" + " <method name=\"GetPlaylists\">\n" + " <arg direction=\"in\" name=\"Index\" type=\"u\">\n" + " </arg>\n" + " <arg direction=\"in\" name=\"MaxCount\" type=\"u\">\n" + " </arg>\n" + " <arg direction=\"in\" name=\"Order\" type=\"s\">\n" + " </arg>\n" + " <arg direction=\"in\" name=\"ReverseOrder\" type=\"b\">\n" + " </arg>\n" + " <arg direction=\"out\" name=\"Playlists\" type=\"a(oss)\">\n" + " </arg>\n" + " </method>\n" + " <property name=\"PlaylistCount\" type=\"u\" access=\"read\">\n" + " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>\n" + " </property>\n" + " <property name=\"Orderings\" type=\"as\" access=\"read\">\n" + " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>\n" + " </property>\n" + " <property name=\"ActivePlaylist\" type=\"(b(oss))\" access=\"read\">\n" + " <annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>\n" + " </property>\n" + " <signal name=\"PlaylistChanged\">\n" + " <arg name=\"Playlist\" type=\"(oss)\">\n" + " </arg>\n" + " </signal>\n" + " </interface>\n" "</node>\n"; static void proxy_method_call_async_callback(GObject *source_object, GAsyncResult *res, @@ -536,6 +759,10 @@ g_object_unref(reply); } +/** + * Implement MPRIS method calls by delegating to the active player. + * If there is no active player, send an error to our caller. + */ static void player_method_call_proxy_callback(GDBusConnection *connection, const char *sender, const char *object_path, const char *interface_name, const char *method_name, GVariant *parameters, @@ -575,6 +802,9 @@ g_object_unref(message); } +/** + * Implement com.github.altdesktop.playerctld methods + */ static void playerctld_method_call_func(GDBusConnection *connection, const char *sender, const char *object_path, const char *interface_name, const char *method_name, GVariant *parameters, @@ -586,6 +816,11 @@ struct Player *active_player; if (strcmp(method_name, "Shift") == 0) { + /** + * com.github.altdesktop.playerctld.Shift + * Move the active player to the back of the queue, + * return the new active player + */ if ((active_player = context_shift_active_player(ctx))) { g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", active_player->well_known)); @@ -596,6 +831,11 @@ "No player is being controlled by playerctld"); } } else if (strcmp(method_name, "Unshift") == 0) { + /** + * com.github.altdesktop.playerctld.Unshift + * Set the least recently active player to active, + * return that player. Inverse of Shift. + */ if ((active_player = context_unshift_active_player(ctx))) { g_dbus_method_invocation_return_value(invocation, g_variant_new("(s)", active_player->well_known)); @@ -606,19 +846,26 @@ "No player is being controlled by playerctld"); } } else { + /** + * Fail on unknown methods. + */ g_dbus_method_invocation_return_dbus_error(invocation, "com.github.altdesktop.playerctld.InvalidMethod", "This method is not valid"); } } +/** + * Property getter for com.github.altdesktop.playerctld + */ static GVariant *playerctld_get_property_func(GDBusConnection *connection, const gchar *sender, const gchar *object_path, const gchar *interface_name, const gchar *property_name, GError **error, gpointer user_data) { struct PlayerctldContext *ctx = user_data; if (g_strcmp0(property_name, "PlayerNames") != 0) { - // glib library error + // Fail on unknown properties. + // FIXME: Should return a DBus error and not crash g_error("unknown property: %s", property_name); assert(false); } @@ -626,26 +873,48 @@ return context_player_names_to_gvariant(ctx); } -static GDBusInterfaceVTable vtable_player = {player_method_call_proxy_callback, NULL, NULL, {0}}; - -static GDBusInterfaceVTable vtable_root = {player_method_call_proxy_callback, NULL, NULL, {0}}; +/** + * Location of implementation of MPRIS interfaces + */ +static GDBusInterfaceVTable vtable_mpris = {player_method_call_proxy_callback, NULL, NULL, {0}}; +/** + * Location of implementation of com.github.altdesktop.playerctld interface + */ static GDBusInterfaceVTable vtable_playerctld = { playerctld_method_call_func, playerctld_get_property_func, NULL, {0}}; +/** + * Register callbacks to implement the interfaces we're supposed to + * That is to say, the four MPRIS interfaces as well as com.github.altdesktop.playerctld + */ static void on_bus_acquired(GDBusConnection *connection, const char *name, gpointer user_data) { GError *error = NULL; struct PlayerctldContext *ctx = user_data; g_dbus_connection_register_object(connection, MPRIS_PATH, ctx->root_interface_info, - &vtable_root, user_data, NULL, &error); + &vtable_mpris, user_data, NULL, &error); if (error != NULL) { g_warning("%s", error->message); g_clear_error(&error); } g_dbus_connection_register_object(connection, MPRIS_PATH, ctx->player_interface_info, - &vtable_player, user_data, NULL, &error); + &vtable_mpris, user_data, NULL, &error); + if (error != NULL) { + g_warning("%s", error->message); + g_clear_error(&error); + } + + g_dbus_connection_register_object(connection, MPRIS_PATH, ctx->playlists_interface_info, + &vtable_mpris, user_data, NULL, &error); + if (error != NULL) { + g_warning("%s", error->message); + g_clear_error(&error); + } + + g_dbus_connection_register_object(connection, MPRIS_PATH, ctx->tracklist_interface_info, + &vtable_mpris, user_data, NULL, &error); if (error != NULL) { g_warning("%s", error->message); g_clear_error(&error); @@ -792,6 +1061,26 @@ g_variant_new("(s)", ROOT_INTERFACE), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, active_player_get_properties_async_callback, root_data); + + struct GetPropertiesUserData *tracklist_data = + calloc(1, sizeof(struct GetPropertiesUserData)); + tracklist_data->interface_name = TRACKLIST_INTERFACE; + tracklist_data->player = player; + tracklist_data->ctx = ctx; + g_dbus_connection_call(connection, new_owner, MPRIS_PATH, PROPERTIES_INTERFACE, "GetAll", + g_variant_new("(s)", TRACKLIST_INTERFACE), NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, + active_player_get_properties_async_callback, tracklist_data); + + struct GetPropertiesUserData *playlists_data = + calloc(1, sizeof(struct GetPropertiesUserData)); + playlists_data->interface_name = PLAYLISTS_INTERFACE; + playlists_data->player = player; + playlists_data->ctx = ctx; + g_dbus_connection_call(connection, new_owner, MPRIS_PATH, PROPERTIES_INTERFACE, "GetAll", + g_variant_new("(s)", PLAYLISTS_INTERFACE), NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, + active_player_get_properties_async_callback, playlists_data); } else { struct Player *player = context_find_player(ctx, NULL, name); if (player == NULL) { @@ -846,6 +1135,9 @@ return; } + g_debug("got player signal: sender=%s, object_path=%s, interface_name=%s, signal_name=%s", + sender_name, object_path, interface_name, signal_name); + if (g_strcmp0(interface_name, PLAYER_INTERFACE) != 0 && g_strcmp0(interface_name, PROPERTIES_INTERFACE) != 0) { return; @@ -1008,6 +1300,7 @@ exit(0); } + // Setup DBus connection GDBusConnectionFlags connection_flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION; @@ -1083,11 +1376,30 @@ } ctx.root_interface_info = g_dbus_node_info_lookup_interface(mpris_introspection_data, ROOT_INTERFACE); + if (ctx.root_interface_info == NULL) { + g_error("Missing introspection data for MPRIS root interface"); + } + // Is the player interface missing from the introspection data? ctx.player_interface_info = g_dbus_node_info_lookup_interface(mpris_introspection_data, PLAYER_INTERFACE); + if (ctx.player_interface_info == NULL) { + g_error("Missing introspection data for MPRIS player interface"); + } + ctx.playlists_interface_info = + g_dbus_node_info_lookup_interface(mpris_introspection_data, PLAYLISTS_INTERFACE); + if (ctx.playlists_interface_info == NULL) { + g_error("Missing introspection data for MPRIS playlists interface"); + } + ctx.tracklist_interface_info = + g_dbus_node_info_lookup_interface(mpris_introspection_data, TRACKLIST_INTERFACE); + if (ctx.tracklist_interface_info == NULL) { + g_error("Missing introspection data for MPRIS tracklist interface"); + } ctx.playerctld_interface_info = g_dbus_node_info_lookup_interface( playerctld_introspection_data, "com.github.altdesktop.playerctld"); + // Get all names of players (names that start with "org.mpris.MediaPlayer2.") + // then fetch their properties on all supported interfaces GVariant *names_reply = g_dbus_connection_call_sync( ctx.connection, DBUS_NAME, DBUS_PATH, DBUS_INTERFACE, "ListNames", NULL, NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); @@ -1100,6 +1412,7 @@ const gchar **names = g_variant_get_strv(names_reply_value, &nnames); for (int i = 0; i < nnames; ++i) { if (well_known_name_is_managed(names[i])) { + // org.mpris.MediaPlayer2.Player properties GVariant *owner_reply = g_dbus_connection_call_sync(ctx.connection, DBUS_NAME, DBUS_PATH, DBUS_INTERFACE, "GetNameOwner", g_variant_new("(s)", names[i]), NULL, @@ -1120,6 +1433,7 @@ g_variant_new("(s)", PLAYER_INTERFACE), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); if (error != NULL) { + // This interface is mandatory, get rid of "players" who don't support it g_warning("could not get player properties for player: %s", player->well_known); player_free(player); g_clear_error(&error); @@ -1131,11 +1445,13 @@ g_variant_unref(properties); g_variant_unref(reply); + // org.mpris.MediaPlayer2 properties reply = g_dbus_connection_call_sync(ctx.connection, player->unique, MPRIS_PATH, PROPERTIES_INTERFACE, "GetAll", g_variant_new("(s)", ROOT_INTERFACE), NULL, G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); if (error != NULL) { + // This interface is mandatory, get rid of "players" who don't support it g_warning("could not get root properties for player: %s", player->well_known); player_free(player); g_clear_error(&error); @@ -1147,6 +1463,42 @@ g_variant_unref(properties); g_variant_unref(reply); + // org.mpris.MediaPlayer2.TrackList properties + player->tracklist.supported = true; // Or so we hope + reply = g_dbus_connection_call_sync(ctx.connection, player->unique, MPRIS_PATH, + PROPERTIES_INTERFACE, "GetAll", + g_variant_new("(s)", TRACKLIST_INTERFACE), NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); + if (error != NULL) { + // This interface is optional, so we can keep the player around + player->tracklist.supported = false; + g_warning("could not get tracklist properties for player: %s", player->well_known); + g_clear_error(&error); + } else { + properties = g_variant_get_child_value(reply, 0); + player_update_properties(player, TRACKLIST_INTERFACE, properties); + g_variant_unref(properties); + g_variant_unref(reply); + } + + // org.mpris.MediaPlayer2.Playlists properties + player->playlists.supported = true; // Or so we hope + reply = g_dbus_connection_call_sync(ctx.connection, player->unique, MPRIS_PATH, + PROPERTIES_INTERFACE, "GetAll", + g_variant_new("(s)", PLAYLISTS_INTERFACE), NULL, + G_DBUS_CALL_FLAGS_NO_AUTO_START, -1, NULL, &error); + if (error != NULL) { + // This interface is optional, so we can keep the player around + player->playlists.supported = false; + g_warning("could not get playlists properties for player: %s", player->well_known); + g_clear_error(&error); + } else { + properties = g_variant_get_child_value(reply, 0); + player_update_properties(player, PLAYLISTS_INTERFACE, properties); + g_variant_unref(properties); + g_variant_unref(reply); + } + g_debug("found player: %s", player->well_known); g_queue_push_head(ctx.players, player); g_variant_unref(owner_reply_value); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/playerctl/playerctl-formatter.c new/playerctl-2.4.1/playerctl/playerctl-formatter.c --- old/playerctl-2.3.1/playerctl/playerctl-formatter.c 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/playerctl/playerctl-formatter.c 2021-09-21 14:23:38.000000000 +0200 @@ -93,7 +93,9 @@ } break; case TOKEN_FUNCTION: - return token_list_contains_key(token->args, key); + if (token_list_contains_key(token->args, key)) { + return TRUE; + } default: break; } @@ -481,14 +483,23 @@ return g_variant_new("s", ""); } - // mpris durations are represented as int64 in microseconds - if (!g_variant_type_equal(g_variant_get_type(value), G_VARIANT_TYPE_INT64)) { + gint64 duration; + + if (g_variant_type_equal(g_variant_get_type(value), G_VARIANT_TYPE_INT64)) { + // mpris specifies all track position values to be int64 + duration = g_variant_get_int64(value); + } else if (g_variant_type_equal(g_variant_get_type(value), G_VARIANT_TYPE_UINT64)) { + // XXX: spotify may give uint64 + duration = g_variant_get_uint64(value); + } else if (g_variant_type_equal(g_variant_get_type(value), G_VARIANT_TYPE_DOUBLE)) { + // only if supplied by a constant or position value type goes against spec + duration = g_variant_get_double(value); + } else { g_set_error(error, playerctl_formatter_error_quark(), 1, - "function position can only be called on int64 values"); + "function duration can only be called on track position values"); return NULL; } - gint64 duration = g_variant_get_int64(value); gint64 seconds = (duration / 1000000) % 60; gint64 minutes = (duration / 1000000 / 60) % 60; gint64 hours = (duration / 1000000 / 60 / 60); @@ -608,15 +619,55 @@ return value; } +static GVariant *helperfn_trunc(struct token *token, GVariant **args, int nargs, GError **error) { + if (nargs != 2) { + g_set_error(error, playerctl_formatter_error_quark(), 1, + "function trunc takes exactly two arguments (got %d)", nargs); + return NULL; + } + + GVariant *value = args[0]; + GVariant *len = args[1]; + if (value == NULL || len == NULL) { + return g_variant_new("s", ""); + } + + if (!g_variant_type_equal(g_variant_get_type(len), G_VARIANT_TYPE_DOUBLE)) { + g_set_error(error, playerctl_formatter_error_quark(), 1, + "function trunc's length parameter can only be called with an int"); + return NULL; + } + + gchar *orig = pctl_print_gvariant(value); + gchar *trunc = g_utf8_substring(orig, 0, g_variant_get_double(len)); + + GString *formatted = g_string_new(trunc); + if (g_utf8_strlen(trunc, 256) < g_utf8_strlen(orig, 256)) { + g_string_append(formatted, "???"); + } + + gchar *formatted_inner = g_string_free(formatted, FALSE); + GVariant *ret = g_variant_new("s", formatted_inner); + g_free(formatted_inner); + g_free(trunc); + g_free(orig); + + return ret; +} + static gboolean is_valid_numeric_type(GVariant *value) { // This is all the types we know about for numeric operations. May be - // expanded at a later time. + // expanded at a later time. MPRIS only uses INT64 and DOUBLE as numeric + // types. Formatter constants are always DOUBLE. All other types are for + // player workarounds. if (value == NULL) { return FALSE; } if (g_variant_is_of_type(value, G_VARIANT_TYPE_INT64)) { return TRUE; + } else if (g_variant_is_of_type(value, G_VARIANT_TYPE_UINT64)) { + return TRUE; } else if (g_variant_is_of_type(value, G_VARIANT_TYPE_DOUBLE)) { return TRUE; } @@ -625,8 +676,12 @@ } static gdouble get_double_value(GVariant *value) { + // Keep this in sync with above is_value_numeric_type() + if (g_variant_is_of_type(value, G_VARIANT_TYPE_INT64)) { return (gdouble)g_variant_get_int64(value); + } else if (g_variant_is_of_type(value, G_VARIANT_TYPE_UINT64)) { + return (gdouble)g_variant_get_uint64(value); } else if (g_variant_is_of_type(value, G_VARIANT_TYPE_DOUBLE)) { return g_variant_get_double(value); } else { @@ -830,6 +885,7 @@ {"markup_escape", &helperfn_markup_escape}, {"default", &helperfn_default}, {"emoji", &helperfn_emoji}, + {"trunc", &helperfn_trunc}, {INFIX_ADD, &infixfn_add}, {INFIX_SUB, &infixfn_sub}, {INFIX_MUL, &infixfn_mul}, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/playerctl/playerctl-player-manager.c new/playerctl-2.4.1/playerctl/playerctl-player-manager.c --- old/playerctl-2.3.1/playerctl/playerctl-player-manager.c 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/playerctl/playerctl-player-manager.c 2021-09-21 14:23:38.000000000 +0200 @@ -249,9 +249,9 @@ } if (!g_variant_is_of_type(parameters, G_VARIANT_TYPE("(sss)"))) { - g_warning("Got unknown parameters on org.freedesktop.DBus " - "NameOwnerChange signal: %s", - g_variant_get_type_string(parameters)); + g_debug("Got unknown parameters on org.freedesktop.DBus " + "NameOwnerChange signal: %s", + g_variant_get_type_string(parameters)); return; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/playerctl/playerctl-player.c new/playerctl-2.4.1/playerctl/playerctl-player.c --- old/playerctl-2.3.1/playerctl/playerctl-player.c 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/playerctl/playerctl-player.c 2021-09-21 14:23:38.000000000 +0200 @@ -124,7 +124,7 @@ // XXX some players set this as a string, which is against the protocol, // but a lot of them do it and I don't feel like fixing it on all the // players in the world. - g_warning("mpris:trackid is a string, not a D-Bus object reference"); + g_debug("mpris:trackid is a string, not a D-Bus object reference"); track_id_variant = g_variant_lookup_value(metadata, "mpris:trackid", G_VARIANT_TYPE_STRING); } @@ -197,7 +197,7 @@ // XXX: Lots of player aren't setting status correctly when the track // changes so we have to get it from the interface. We should // definitely go fix this bug on the players. - g_warning("Playback status not set on track change; getting status from interface instead"); + g_debug("Playback status not set on track change; getting status from interface instead"); GVariant *call_reply = g_dbus_proxy_call_sync( G_DBUS_PROXY(self->priv->proxy), "org.freedesktop.DBus.Properties.Get", g_variant_new("(ss)", "org.mpris.MediaPlayer2.Player", "PlaybackStatus"), @@ -270,7 +270,7 @@ g_signal_emit(self, connection_signals[PLAYBACK_STATUS], quark, status); } } else { - g_warning("%s: got unknown playback state: %s", instance, status_str); + g_debug("%s: got unknown playback state: %s", instance, status_str); } g_variant_unref(playback_status); @@ -306,7 +306,7 @@ if (!metadata) { // XXX: Ugly spotify workaround. Spotify does not seem to use the property // cache. We have to get the properties directly. - g_warning("Spotify does not use the D-Bus property cache, getting properties directly"); + g_debug("Spotify does not use the D-Bus property cache, getting properties directly"); GVariant *call_reply = g_dbus_proxy_call_sync( G_DBUS_PROXY(self->priv->proxy), "org.freedesktop.DBus.Properties.Get", g_variant_new("(ss)", "org.mpris.MediaPlayer2.Player", "Metadata"), @@ -388,7 +388,7 @@ g_value_set_enum(value, status); } else { if (status_str != NULL) { - g_warning("got unknown loop status: %s", status_str); + g_debug("got unknown loop status: %s", status_str); } g_value_set_enum(value, PLAYERCTL_LOOP_STATUS_NONE); } @@ -864,7 +864,7 @@ GList *exact_match = pctl_player_name_find(names, name, pctl_bus_type_to_source(bus_type)); if (exact_match != NULL) { - g_debug("Geting bus name for player %s by exact match", name); + g_debug("Getting bus name for player %s by exact match", name); PlayerctlPlayerName *name = exact_match->data; bus_name = g_strdup_printf(MPRIS_PREFIX "%s", name->instance); g_list_free_full(names, (GDestroyNotify)playerctl_player_name_free); @@ -874,7 +874,7 @@ GList *instance_match = pctl_player_name_find_instance(names, name, pctl_bus_type_to_source(bus_type)); if (instance_match != NULL) { - g_debug("Geting bus name for player %s by instance match", name); + g_debug("Getting bus name for player %s by instance match", name); gchar *name = instance_match->data; bus_name = g_strdup_printf(MPRIS_PREFIX "%s", name); pctl_player_name_list_destroy(names); @@ -939,7 +939,7 @@ bus_name_for_player_name(player->priv->player_name, bus_types[i], &tmp_error); if (tmp_error != NULL) { if (tmp_error->domain == G_IO_ERROR && tmp_error->code == G_IO_ERROR_NOT_FOUND) { - g_warning("Bus address set incorrectly, cannot get bus"); + g_debug("Bus address set incorrectly, cannot get bus"); g_clear_error(&tmp_error); continue; } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/playerctl/playerctl.h new/playerctl-2.4.1/playerctl/playerctl.h --- old/playerctl-2.3.1/playerctl/playerctl.h 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/playerctl/playerctl.h 2021-09-21 14:23:38.000000000 +0200 @@ -22,12 +22,19 @@ #define __PLAYERCTL_INSIDE__ +#ifdef __cplusplus +extern "C" { +#endif + #include <playerctl/playerctl-enum-types.h> #include <playerctl/playerctl-player-manager.h> #include <playerctl/playerctl-player-name.h> #include <playerctl/playerctl-player.h> #include <playerctl/playerctl-version.h> +#ifdef __cplusplus +} // extern "C" +#endif #undef __PLAYERCTL_INSIDE__ #endif /* __PLAYERCTL_H__ */ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/snap/snapcraft.yaml new/playerctl-2.4.1/snap/snapcraft.yaml --- old/playerctl-2.3.1/snap/snapcraft.yaml 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/snap/snapcraft.yaml 2021-09-21 14:23:38.000000000 +0200 @@ -30,15 +30,20 @@ interface: dbus bus: session name: org.mpris.MediaPlayer2.playerctld + system-observe: + interface: system-observe + desktop-legacy: + interface: desktop-legacy apps: playerctl: command: playerctl + slots: [ system-observe, desktop-legacy ] environment: LD_LIBRARY_PATH: $SNAP/usr/local/lib/$SNAPCRAFT_ARCH_TRIPLET playerctld: command: playerctld - slots: [ dbus-svc ] + slots: [ dbus-svc, system-observe, desktop-legacy ] environment: LD_LIBRARY_PATH: $SNAP/usr/local/lib/$SNAPCRAFT_ARCH_TRIPLET diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/playerctl-2.3.1/test/test_format.py new/playerctl-2.4.1/test/test_format.py --- old/playerctl-2.3.1/test/test_format.py 2020-11-30 20:06:59.000000000 +0100 +++ new/playerctl-2.4.1/test/test_format.py 2021-09-21 14:23:38.000000000 +0200 @@ -114,6 +114,10 @@ test.add(' {{lc(album)}} ', album.lower()) test.add('{{playerName}} - {{playerInstance}}', f'{player_name} - {player_instance}') + test.add("{{trunc(title, 10)}}", title) + test.add("{{trunc(title, 5)}}", f"{title[:5]}???") + test.add('{{trunc("", 0)}}', "") + test.add('{{trunc("", 10)}}', "") await test.run()
