Skip to content

Commit

Permalink
Use playerctld to get player names when available
Browse files Browse the repository at this point in the history
fixes #192
  • Loading branch information
Tony Crisci committed Nov 7, 2020
1 parent a1cfd4a commit bfed117
Show file tree
Hide file tree
Showing 13 changed files with 245 additions and 91 deletions.
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ RUN pip3 install -r requirements.txt

ADD . /app

COPY data/test/dbus-system.conf /etc/dbus-1/system.d/test-dbus-system.conf
COPY test/data/dbus-system.conf /etc/dbus-1/system.d/test-dbus-system.conf

RUN meson --prefix=/usr build && \
ninja -C build && ninja -C build install
RUN mkdir -p /run/dbus
ENV PYTHONASYNCIODEBUG=1
ENV DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket
CMD ["bash", "-c", "dbus-daemon --nopidfile --system && dbus-run-session python3 -m pytest -vv"]
CMD ["bash", "-c", "dbus-daemon --nopidfile --system && dbus-run-session python3 -m pytest -vvs"]
11 changes: 2 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Here is a list of available commands:

Without specifying any players to control, Playerctl will act on the first player it can find.

Playerctl comes with a service called `playerctld` that monitors the activity of media players in the background. If `playerctld` is running, Playerctl will act on players in order of their last activity.

You can list the names of players that are available to control that are running on the system with `playerctl --list-all`.

If you'd only like to control certain players, you can pass the names of those players separated by commas with the `--player` flag. Playerctl will select the first instance of a player in that list that supports the command. To control all players in the list, you can use the `--all-players` flag.
Expand Down Expand Up @@ -69,15 +71,6 @@ playerctl --player=%any,chromium play
playerctl --player=vlc,%any play
```

#### Selecting the Most Recent Player

Playerctl comes with a service called `playerctld` you can use that monitors the activity of media players to select the one with the most recent activity. To use it, simply pass `playerctld` as the selected player to Playerctl and the service should start automatically (if it doesn't, see the troubleshooting section).

```
# Command the most recent player to play
playerctl --player=playerctld play
```

### Printing Properties and Metadata

You can pass a format string with the `--format` argument to print properties in a specific format. Pass the variable you want to print in the format string between double braces like `{{ VARIABLE }}`. The variables available are either the name of the query command, or anything in the metadata map which can be viewed with `playerctl metadata`. You can use this to integrate playerctl into a statusline generator.
Expand Down
140 changes: 74 additions & 66 deletions playerctl/playerctl-cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,7 @@ static gboolean playercmd_metadata(PlayerctlPlayer *player, gchar **argv, gint a
return TRUE;
}

static void managed_player_properties_callback(PlayerctlPlayer *player, gpointer *data) {
static void managed_player_properties_callback(PlayerctlPlayer *player, gpointer data) {
playerctl_player_manager_move_player_to_top(manager, player);
GError *error = NULL;
managed_players_execute_command(&error);
Expand Down Expand Up @@ -883,6 +883,28 @@ static GList *parse_player_list(gchar *player_list_arg) {
return players;
}

static gboolean name_is_selected(const gchar *name) {
if (ignored_player_names != NULL) {
gboolean ignored =
(g_list_find_custom(ignored_player_names, name,
(GCompareFunc)pctl_player_name_string_instance_compare) != NULL);
if (ignored) {
return FALSE;
}
}

if (player_names != NULL) {
gboolean selected =
(g_list_find_custom(player_names, name,
(GCompareFunc)pctl_player_name_string_instance_compare) != NULL);
if (!selected) {
return FALSE;
}
}

return TRUE;
}

static int handle_version_flag() {
g_print("v%s\n", PLAYERCTL_VERSION_S);
return 0;
Expand All @@ -892,22 +914,26 @@ static int handle_list_all_flag() {
GError *tmp_error = NULL;
GList *player_names_list = playerctl_list_players(&tmp_error);

player_names_list =
g_list_sort_with_data(player_names_list, player_name_compare_func, (gpointer)player_names);

if (tmp_error != NULL) {
g_printerr("%s\n", tmp_error->message);
return 1;
}

if (player_names_list == NULL) {
if (!no_status_error_messages) {
g_printerr("No players were found\n");
}
return 0;
}

gboolean one_selected = FALSE;
GList *l = NULL;
for (l = player_names_list; l != NULL; l = l->next) {
PlayerctlPlayerName *name = l->data;
printf("%s\n", name->instance);
if (name_is_selected(name->instance)) {
one_selected = TRUE;
printf("%s\n", name->instance);
}
}

if (!one_selected && !no_status_error_messages) {
g_printerr("No players were found\n");
}

pctl_player_name_list_destroy(player_names_list);
Expand Down Expand Up @@ -958,28 +984,6 @@ static void managed_players_execute_command(GError **error) {
}
}

static gboolean name_is_selected(gchar *name) {
if (ignored_player_names != NULL) {
gboolean ignored =
(g_list_find_custom(ignored_player_names, name,
(GCompareFunc)pctl_player_name_string_instance_compare) != NULL);
if (ignored) {
return FALSE;
}
}

if (player_names != NULL) {
gboolean selected =
(g_list_find_custom(player_names, name,
(GCompareFunc)pctl_player_name_string_instance_compare) != NULL);
if (!selected) {
return FALSE;
}
}

return TRUE;
}

static void name_appeared_callback(PlayerctlPlayerManager *manager, PlayerctlPlayerName *name,
gpointer *data) {
if (!name_is_selected(name->instance)) {
Expand Down Expand Up @@ -1074,75 +1078,76 @@ static void player_vanished_callback(PlayerctlPlayerManager *manager, PlayerctlP
}
}

gint player_name_string_compare_func(gconstpointer a, gconstpointer b) {
gint player_name_string_compare_func(gconstpointer a, gconstpointer b, gpointer user_data) {
const gchar *name_a = a;
const gchar *name_b = b;
GList *names = user_data;

if (g_strcmp0(name_a, name_b) == 0) {
return 0;
}

int a_index = -1;
int b_index = -1;
int a_match_index = -1;
int b_match_index = -1;

int any_index = INT_MAX;
int i = 0;
GList *l = NULL;
for (l = player_names; l != NULL; l = l->next) {
for (l = names; l != NULL; l = l->next) {
gchar *name = l->data;

if (g_strcmp0(name, "%any") == 0) {
if (any_index == INT_MAX) {
any_index = i;
}
} else if (g_strcmp0(name_a, name) == 0) {
if (a_index == -1) {
a_index = i;
}
} else if (g_strcmp0(name_b, name) == 0) {
if (b_index == -1) {
b_index = i;
}
} else if (pctl_player_name_string_instance_compare(name, name_a) == 0) {
if (a_index == -1) {
a_index = i;
continue;
}

if (pctl_player_name_string_instance_compare(name, name_a) == 0) {
if (a_match_index == -1) {
a_match_index = i;
}
} else if (pctl_player_name_string_instance_compare(name, name_b) == 0) {
if (b_index == -1) {
b_index = i;
}

if (pctl_player_name_string_instance_compare(name, name_b) == 0) {
if (b_match_index == -1) {
b_match_index = i;
}
}
++i;
}

if (a_index == -1 && b_index == -1) {
if (a_match_index == -1 && b_match_index == -1) {
// neither are in the list
return 0;
} else if (a_index == -1) {
} else if (a_match_index == -1) {
// b is in the list
return (b_index < any_index ? 1 : -1);
} else if (b_index == -1) {
return (b_match_index < any_index ? 1 : -1);
} else if (b_match_index == -1) {
// a is in the list
return (a_index < any_index ? -1 : 1);
return (a_match_index < any_index ? -1 : 1);
} else if (a_match_index == b_match_index) {
// preserve order
return 0;
} else {
// both are in the list
return (a_index < b_index ? -1 : 1);
return (a_match_index < b_match_index ? -1 : 1);
}
}

gint player_name_compare_func(gconstpointer a, gconstpointer b) {
gint player_name_compare_func(gconstpointer a, gconstpointer b, gpointer user_data) {
const PlayerctlPlayerName *name_a = a;
const PlayerctlPlayerName *name_b = b;
return player_name_string_compare_func(name_a->instance, name_b->instance);
return player_name_string_compare_func(name_a->instance, name_b->instance, user_data);
}

gint player_compare_func(gconstpointer a, gconstpointer b) {
gint player_compare_func(gconstpointer a, gconstpointer b, gpointer user_data) {
PlayerctlPlayer *player_a = PLAYERCTL_PLAYER(a);
PlayerctlPlayer *player_b = PLAYERCTL_PLAYER(b);
gchar *name_a = NULL;
gchar *name_b = NULL;
g_object_get(player_a, "player-name", &name_a, NULL);
g_object_get(player_b, "player-name", &name_b, NULL);
gint result = player_name_string_compare_func(name_a, name_b);
gint result = player_name_string_compare_func(name_a, name_b, user_data);
g_free(name_a);
g_free(name_b);
return result;
Expand All @@ -1163,6 +1168,9 @@ int main(int argc, char *argv[]) {
exit(0);
}

player_names = parse_player_list(player_arg);
ignored_player_names = parse_player_list(ignore_player_arg);

if (print_version_and_exit) {
int result = handle_version_flag();
exit(result);
Expand Down Expand Up @@ -1191,8 +1199,6 @@ int main(int argc, char *argv[]) {
}
}

player_names = parse_player_list(player_arg);
ignored_player_names = parse_player_list(ignore_player_arg);
playercmd_args = playercmd_args_create(command_arg, num_commands);

manager = playerctl_player_manager_new(&error);
Expand All @@ -1203,13 +1209,14 @@ int main(int argc, char *argv[]) {
}

if (player_names != NULL && !select_all_players) {
playerctl_player_manager_set_sort_func(manager, (GCompareDataFunc)player_compare_func, NULL,
playerctl_player_manager_set_sort_func(manager, player_compare_func, (gpointer)player_names,
NULL);
}

g_object_get(manager, "player-names", &available_players, NULL);
available_players = g_list_copy(available_players);
available_players = g_list_sort(available_players, (GCompareFunc)player_name_compare_func);
available_players =
g_list_sort_with_data(available_players, player_name_compare_func, (gpointer)player_names);

PlayerctlPlayerName playerctld_name = {
.instance = "playerctld",
Expand All @@ -1222,10 +1229,11 @@ int main(int argc, char *argv[]) {
// playerctld is not ignored, was specified exactly in the list of
// players, and is not in the list of available players. Add it to the
// list and try to autostart it.
g_debug("%s", "playerctld was selected and is not available, attempting to autostart it");
g_debug("%s", "playerctld was selected explicitly, it may autostart");
available_players = g_list_append(
available_players, pctl_player_name_new("playerctld", PLAYERCTL_SOURCE_DBUS_SESSION));
available_players = g_list_sort(available_players, (GCompareFunc)player_name_compare_func);
available_players = g_list_sort_with_data(available_players, player_name_compare_func,
(gpointer)player_names);
}

gboolean has_selected = FALSE;
Expand Down
56 changes: 54 additions & 2 deletions playerctl/playerctl-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@

#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>

#define PLAYERCTLD_BUS_NAME "org.mpris.MediaPlayer2.playerctld"

gboolean pctl_parse_playback_status(const gchar *status_str, PlayerctlPlaybackStatus *status) {
if (status_str == NULL) {
return FALSE;
Expand Down Expand Up @@ -162,8 +165,9 @@ gint pctl_player_name_string_instance_compare(const gchar *name, const gchar *in
}

gboolean exact_match = (g_strcmp0(name, instance) == 0);
gboolean instance_match = !exact_match && (g_str_has_prefix(instance, name) &&
g_str_has_prefix(instance + strlen(name), "."));
gboolean instance_match =
!exact_match && (g_str_has_prefix(instance, name) && strlen(instance) > strlen(name) &&
g_str_has_prefix(instance + strlen(name), "."));

if (exact_match || instance_match) {
return 0;
Expand Down Expand Up @@ -191,12 +195,16 @@ GList *pctl_player_name_find_instance(GList *list, gchar *player_id, PlayerctlSo
}

void pctl_player_name_list_destroy(GList *list) {
if (list == NULL) {
return;
}
g_list_free_full(list, (GDestroyNotify)playerctl_player_name_free);
}

GList *pctl_list_player_names_on_bus(GBusType bus_type, GError **err) {
GError *tmp_error = NULL;
GList *players = NULL;
gboolean has_playerctld = FALSE;

GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync(
bus_type, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus",
Expand Down Expand Up @@ -237,6 +245,46 @@ GList *pctl_list_player_names_on_bus(GBusType bus_type, GError **err) {
gsize reply_count;
const gchar **names = g_variant_get_strv(reply_child, &reply_count);

// If playerctld is in the names, get the list of players from there
// because it will be in order of activity
for (gsize i = 0; i < reply_count; i += 1) {
if (g_strcmp0(names[i], PLAYERCTLD_BUS_NAME) == 0) {
g_debug("%s", "Playerctld is running. Getting names from there.");
has_playerctld = TRUE;

GDBusProxy *playerctld_proxy = g_dbus_proxy_new_for_bus_sync(
bus_type, G_DBUS_PROXY_FLAGS_NONE, NULL, PLAYERCTLD_BUS_NAME,
"/org/mpris/MediaPlayer2", "com.github.altdesktop.playerctld", NULL, &tmp_error);
if (tmp_error != NULL) {
g_warning("Could not get player names from playerctld: %s", tmp_error->message);
g_clear_error(&tmp_error);
g_object_unref(playerctld_proxy);
break;
}

GVariant *playerctld_reply =
g_dbus_proxy_get_cached_property(playerctld_proxy, "PlayerNames");
if (playerctld_reply == NULL) {
g_warning(
"%s",
"Could not get player names from playerctld: PlayerNames property not found");
g_clear_error(&tmp_error);
g_object_unref(playerctld_proxy);
break;
}

g_variant_unref(reply);
g_free(names);

reply = playerctld_reply;
names = g_variant_get_strv(reply, &reply_count);
g_object_unref(playerctld_proxy);
has_playerctld = TRUE;

break;
}
}

size_t offset = strlen(MPRIS_PREFIX);
for (gsize i = 0; i < reply_count; i += 1) {
if (g_str_has_prefix(names[i], MPRIS_PREFIX)) {
Expand All @@ -246,6 +294,10 @@ GList *pctl_list_player_names_on_bus(GBusType bus_type, GError **err) {
}
}

if (!has_playerctld) {
players = g_list_sort(players, (GCompareFunc)pctl_player_name_compare);
}

g_object_unref(proxy);
g_variant_unref(reply);
g_variant_unref(reply_child);
Expand Down
Loading

0 comments on commit bfed117

Please sign in to comment.