@@ -20,11 +20,11 @@ RUN pip3 install -r requirements.txt
|
|||||||
|
|
||||||
ADD . /app
|
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 && \
|
RUN meson --prefix=/usr build && \
|
||||||
ninja -C build && ninja -C build install
|
ninja -C build && ninja -C build install
|
||||||
RUN mkdir -p /run/dbus
|
RUN mkdir -p /run/dbus
|
||||||
ENV PYTHONASYNCIODEBUG=1
|
ENV PYTHONASYNCIODEBUG=1
|
||||||
ENV DBUS_SYSTEM_BUS_ADDRESS=unix:path=/var/run/dbus/system_bus_socket
|
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
README.md
11
README.md
@@ -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.
|
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`.
|
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.
|
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.
|
||||||
@@ -69,15 +71,6 @@ playerctl --player=%any,chromium play
|
|||||||
playerctl --player=vlc,%any 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
|
### 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.
|
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.
|
||||||
|
@@ -723,7 +723,7 @@ static gboolean playercmd_metadata(PlayerctlPlayer *player, gchar **argv, gint a
|
|||||||
return TRUE;
|
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);
|
playerctl_player_manager_move_player_to_top(manager, player);
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
managed_players_execute_command(&error);
|
managed_players_execute_command(&error);
|
||||||
@@ -883,6 +883,28 @@ static GList *parse_player_list(gchar *player_list_arg) {
|
|||||||
return players;
|
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() {
|
static int handle_version_flag() {
|
||||||
g_print("v%s\n", PLAYERCTL_VERSION_S);
|
g_print("v%s\n", PLAYERCTL_VERSION_S);
|
||||||
return 0;
|
return 0;
|
||||||
@@ -892,22 +914,26 @@ static int handle_list_all_flag() {
|
|||||||
GError *tmp_error = NULL;
|
GError *tmp_error = NULL;
|
||||||
GList *player_names_list = playerctl_list_players(&tmp_error);
|
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) {
|
if (tmp_error != NULL) {
|
||||||
g_printerr("%s\n", tmp_error->message);
|
g_printerr("%s\n", tmp_error->message);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player_names_list == NULL) {
|
gboolean one_selected = FALSE;
|
||||||
if (!no_status_error_messages) {
|
|
||||||
g_printerr("No players were found\n");
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
GList *l = NULL;
|
GList *l = NULL;
|
||||||
for (l = player_names_list; l != NULL; l = l->next) {
|
for (l = player_names_list; l != NULL; l = l->next) {
|
||||||
PlayerctlPlayerName *name = l->data;
|
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);
|
pctl_player_name_list_destroy(player_names_list);
|
||||||
@@ -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,
|
static void name_appeared_callback(PlayerctlPlayerManager *manager, PlayerctlPlayerName *name,
|
||||||
gpointer *data) {
|
gpointer *data) {
|
||||||
if (!name_is_selected(name->instance)) {
|
if (!name_is_selected(name->instance)) {
|
||||||
@@ -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_a = a;
|
||||||
const gchar *name_b = b;
|
const gchar *name_b = b;
|
||||||
|
GList *names = user_data;
|
||||||
|
|
||||||
if (g_strcmp0(name_a, name_b) == 0) {
|
if (g_strcmp0(name_a, name_b) == 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int a_index = -1;
|
int a_match_index = -1;
|
||||||
int b_index = -1;
|
int b_match_index = -1;
|
||||||
|
|
||||||
int any_index = INT_MAX;
|
int any_index = INT_MAX;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
GList *l = NULL;
|
GList *l = NULL;
|
||||||
for (l = player_names; l != NULL; l = l->next) {
|
for (l = names; l != NULL; l = l->next) {
|
||||||
gchar *name = l->data;
|
gchar *name = l->data;
|
||||||
|
|
||||||
if (g_strcmp0(name, "%any") == 0) {
|
if (g_strcmp0(name, "%any") == 0) {
|
||||||
if (any_index == INT_MAX) {
|
if (any_index == INT_MAX) {
|
||||||
any_index = i;
|
any_index = i;
|
||||||
}
|
}
|
||||||
} else if (g_strcmp0(name_a, name) == 0) {
|
continue;
|
||||||
if (a_index == -1) {
|
}
|
||||||
a_index = i;
|
|
||||||
|
if (pctl_player_name_string_instance_compare(name, name_a) == 0) {
|
||||||
|
if (a_match_index == -1) {
|
||||||
|
a_match_index = i;
|
||||||
}
|
}
|
||||||
} else if (g_strcmp0(name_b, name) == 0) {
|
}
|
||||||
if (b_index == -1) {
|
|
||||||
b_index = i;
|
if (pctl_player_name_string_instance_compare(name, name_b) == 0) {
|
||||||
}
|
if (b_match_index == -1) {
|
||||||
} else if (pctl_player_name_string_instance_compare(name, name_a) == 0) {
|
b_match_index = i;
|
||||||
if (a_index == -1) {
|
|
||||||
a_index = i;
|
|
||||||
}
|
|
||||||
} else if (pctl_player_name_string_instance_compare(name, name_b) == 0) {
|
|
||||||
if (b_index == -1) {
|
|
||||||
b_index = i;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (a_index == -1 && b_index == -1) {
|
if (a_match_index == -1 && b_match_index == -1) {
|
||||||
// neither are in the list
|
// neither are in the list
|
||||||
return 0;
|
return 0;
|
||||||
} else if (a_index == -1) {
|
} else if (a_match_index == -1) {
|
||||||
// b is in the list
|
// b is in the list
|
||||||
return (b_index < any_index ? 1 : -1);
|
return (b_match_index < any_index ? 1 : -1);
|
||||||
} else if (b_index == -1) {
|
} else if (b_match_index == -1) {
|
||||||
// a is in the list
|
// 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 {
|
} else {
|
||||||
// both are in the list
|
return (a_match_index < b_match_index ? -1 : 1);
|
||||||
return (a_index < b_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_a = a;
|
||||||
const PlayerctlPlayerName *name_b = b;
|
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_a = PLAYERCTL_PLAYER(a);
|
||||||
PlayerctlPlayer *player_b = PLAYERCTL_PLAYER(b);
|
PlayerctlPlayer *player_b = PLAYERCTL_PLAYER(b);
|
||||||
gchar *name_a = NULL;
|
gchar *name_a = NULL;
|
||||||
gchar *name_b = NULL;
|
gchar *name_b = NULL;
|
||||||
g_object_get(player_a, "player-name", &name_a, NULL);
|
g_object_get(player_a, "player-name", &name_a, NULL);
|
||||||
g_object_get(player_b, "player-name", &name_b, 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_a);
|
||||||
g_free(name_b);
|
g_free(name_b);
|
||||||
return result;
|
return result;
|
||||||
@@ -1163,6 +1168,9 @@ int main(int argc, char *argv[]) {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
player_names = parse_player_list(player_arg);
|
||||||
|
ignored_player_names = parse_player_list(ignore_player_arg);
|
||||||
|
|
||||||
if (print_version_and_exit) {
|
if (print_version_and_exit) {
|
||||||
int result = handle_version_flag();
|
int result = handle_version_flag();
|
||||||
exit(result);
|
exit(result);
|
||||||
@@ -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);
|
playercmd_args = playercmd_args_create(command_arg, num_commands);
|
||||||
|
|
||||||
manager = playerctl_player_manager_new(&error);
|
manager = playerctl_player_manager_new(&error);
|
||||||
@@ -1203,13 +1209,14 @@ int main(int argc, char *argv[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (player_names != NULL && !select_all_players) {
|
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);
|
NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_object_get(manager, "player-names", &available_players, NULL);
|
g_object_get(manager, "player-names", &available_players, NULL);
|
||||||
available_players = g_list_copy(available_players);
|
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 = {
|
PlayerctlPlayerName playerctld_name = {
|
||||||
.instance = "playerctld",
|
.instance = "playerctld",
|
||||||
@@ -1222,10 +1229,11 @@ int main(int argc, char *argv[]) {
|
|||||||
// playerctld is not ignored, was specified exactly in the list of
|
// 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
|
// players, and is not in the list of available players. Add it to the
|
||||||
// list and try to autostart it.
|
// 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 = g_list_append(
|
||||||
available_players, pctl_player_name_new("playerctld", PLAYERCTL_SOURCE_DBUS_SESSION));
|
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;
|
gboolean has_selected = FALSE;
|
||||||
|
@@ -21,8 +21,11 @@
|
|||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
|
||||||
|
#define PLAYERCTLD_BUS_NAME "org.mpris.MediaPlayer2.playerctld"
|
||||||
|
|
||||||
gboolean pctl_parse_playback_status(const gchar *status_str, PlayerctlPlaybackStatus *status) {
|
gboolean pctl_parse_playback_status(const gchar *status_str, PlayerctlPlaybackStatus *status) {
|
||||||
if (status_str == NULL) {
|
if (status_str == NULL) {
|
||||||
return FALSE;
|
return FALSE;
|
||||||
@@ -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 exact_match = (g_strcmp0(name, instance) == 0);
|
||||||
gboolean instance_match = !exact_match && (g_str_has_prefix(instance, name) &&
|
gboolean instance_match =
|
||||||
g_str_has_prefix(instance + strlen(name), "."));
|
!exact_match && (g_str_has_prefix(instance, name) && strlen(instance) > strlen(name) &&
|
||||||
|
g_str_has_prefix(instance + strlen(name), "."));
|
||||||
|
|
||||||
if (exact_match || instance_match) {
|
if (exact_match || instance_match) {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -191,12 +195,16 @@ GList *pctl_player_name_find_instance(GList *list, gchar *player_id, PlayerctlSo
|
|||||||
}
|
}
|
||||||
|
|
||||||
void pctl_player_name_list_destroy(GList *list) {
|
void pctl_player_name_list_destroy(GList *list) {
|
||||||
|
if (list == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
g_list_free_full(list, (GDestroyNotify)playerctl_player_name_free);
|
g_list_free_full(list, (GDestroyNotify)playerctl_player_name_free);
|
||||||
}
|
}
|
||||||
|
|
||||||
GList *pctl_list_player_names_on_bus(GBusType bus_type, GError **err) {
|
GList *pctl_list_player_names_on_bus(GBusType bus_type, GError **err) {
|
||||||
GError *tmp_error = NULL;
|
GError *tmp_error = NULL;
|
||||||
GList *players = NULL;
|
GList *players = NULL;
|
||||||
|
gboolean has_playerctld = FALSE;
|
||||||
|
|
||||||
GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync(
|
GDBusProxy *proxy = g_dbus_proxy_new_for_bus_sync(
|
||||||
bus_type, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus",
|
bus_type, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.DBus", "/org/freedesktop/DBus",
|
||||||
@@ -237,6 +245,46 @@ GList *pctl_list_player_names_on_bus(GBusType bus_type, GError **err) {
|
|||||||
gsize reply_count;
|
gsize reply_count;
|
||||||
const gchar **names = g_variant_get_strv(reply_child, &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);
|
size_t offset = strlen(MPRIS_PREFIX);
|
||||||
for (gsize i = 0; i < reply_count; i += 1) {
|
for (gsize i = 0; i < reply_count; i += 1) {
|
||||||
if (g_str_has_prefix(names[i], MPRIS_PREFIX)) {
|
if (g_str_has_prefix(names[i], MPRIS_PREFIX)) {
|
||||||
@@ -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_object_unref(proxy);
|
||||||
g_variant_unref(reply);
|
g_variant_unref(reply);
|
||||||
g_variant_unref(reply_child);
|
g_variant_unref(reply_child);
|
||||||
|
@@ -56,7 +56,7 @@ struct _PlayerctlPlayerManagerPrivate {
|
|||||||
GList *player_names;
|
GList *player_names;
|
||||||
GList *players;
|
GList *players;
|
||||||
GCompareDataFunc sort_func;
|
GCompareDataFunc sort_func;
|
||||||
gpointer *sort_data;
|
gpointer sort_data;
|
||||||
GDestroyNotify sort_notify;
|
GDestroyNotify sort_notify;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -412,7 +412,7 @@ PlayerctlPlayerManager *playerctl_player_manager_new(GError **err) {
|
|||||||
* for using this list as a priority queue.
|
* for using this list as a priority queue.
|
||||||
*/
|
*/
|
||||||
void playerctl_player_manager_set_sort_func(PlayerctlPlayerManager *manager,
|
void playerctl_player_manager_set_sort_func(PlayerctlPlayerManager *manager,
|
||||||
GCompareDataFunc sort_func, gpointer *sort_data,
|
GCompareDataFunc sort_func, gpointer sort_data,
|
||||||
GDestroyNotify notify) {
|
GDestroyNotify notify) {
|
||||||
// TODO figure out how to make this work with the bindings
|
// TODO figure out how to make this work with the bindings
|
||||||
manager->priv->sort_func = sort_func;
|
manager->priv->sort_func = sort_func;
|
||||||
|
@@ -93,7 +93,7 @@ void playerctl_player_manager_manage_player(PlayerctlPlayerManager *manager,
|
|||||||
PlayerctlPlayer *player);
|
PlayerctlPlayer *player);
|
||||||
|
|
||||||
void playerctl_player_manager_set_sort_func(PlayerctlPlayerManager *manager,
|
void playerctl_player_manager_set_sort_func(PlayerctlPlayerManager *manager,
|
||||||
GCompareDataFunc sort_func, gpointer *sort_data,
|
GCompareDataFunc sort_func, gpointer sort_data,
|
||||||
GDestroyNotify notify);
|
GDestroyNotify notify);
|
||||||
|
|
||||||
void playerctl_player_manager_move_player_to_top(PlayerctlPlayerManager *manager,
|
void playerctl_player_manager_move_player_to_top(PlayerctlPlayerManager *manager,
|
||||||
|
@@ -24,4 +24,10 @@
|
|||||||
|
|
||||||
char *pctl_player_get_instance(PlayerctlPlayer *player);
|
char *pctl_player_get_instance(PlayerctlPlayer *player);
|
||||||
|
|
||||||
|
gint player_name_string_compare_func(gconstpointer a, gconstpointer b, gpointer user_data);
|
||||||
|
|
||||||
|
gint player_name_compare_func(gconstpointer a, gconstpointer b, gpointer user_data);
|
||||||
|
|
||||||
|
gint player_compare_func(gconstpointer a, gconstpointer b, gpointer user_data);
|
||||||
|
|
||||||
#endif /* __PLAYERCTL_PLAYER_PRIVATE_H__ */
|
#endif /* __PLAYERCTL_PLAYER_PRIVATE_H__ */
|
||||||
|
@@ -6,6 +6,7 @@ import asyncio
|
|||||||
|
|
||||||
|
|
||||||
async def setup_mpris(*names, bus_address=None, system=False):
|
async def setup_mpris(*names, bus_address=None, system=False):
|
||||||
|
# TODO maybe they should all share a bus for speed
|
||||||
async def setup(name):
|
async def setup(name):
|
||||||
if system:
|
if system:
|
||||||
bus_type = BusType.SYSTEM
|
bus_type = BusType.SYSTEM
|
||||||
@@ -23,6 +24,15 @@ async def setup_mpris(*names, bus_address=None, system=False):
|
|||||||
return await asyncio.gather(*(setup(name) for name in names))
|
return await asyncio.gather(*(setup(name) for name in names))
|
||||||
|
|
||||||
|
|
||||||
|
async def setup_playerctld(bus_address=None):
|
||||||
|
bus = await MessageBus(bus_address=bus_address).connect()
|
||||||
|
playerctld = PlayerctldInterface(bus)
|
||||||
|
bus.export('/org/mpris/MediaPlayer2', playerctld)
|
||||||
|
reply = await bus.request_name('org.mpris.MediaPlayer2.playerctld')
|
||||||
|
assert reply == RequestNameReply.PRIMARY_OWNER
|
||||||
|
return playerctld
|
||||||
|
|
||||||
|
|
||||||
class MprisRoot(ServiceInterface):
|
class MprisRoot(ServiceInterface):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__('org.mpris.MediaPlayer2')
|
super().__init__('org.mpris.MediaPlayer2')
|
||||||
@@ -240,3 +250,18 @@ class MprisPlayer(ServiceInterface):
|
|||||||
@dbus_property(access=PropertyAccess.READ)
|
@dbus_property(access=PropertyAccess.READ)
|
||||||
def CanControl(self) -> 'b':
|
def CanControl(self) -> 'b':
|
||||||
return self.can_control
|
return self.can_control
|
||||||
|
|
||||||
|
|
||||||
|
class PlayerctldInterface(ServiceInterface):
|
||||||
|
'''just enough of playerctld for testing'''
|
||||||
|
def __init__(self, bus):
|
||||||
|
super().__init__('com.github.altdesktop.playerctld')
|
||||||
|
self.bus = bus
|
||||||
|
self.player_names = []
|
||||||
|
|
||||||
|
@dbus_property(access=PropertyAccess.READ)
|
||||||
|
def PlayerNames(self) -> 'as':
|
||||||
|
return self.player_names
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
self.bus.disconnect()
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
|
from shlex import join
|
||||||
|
|
||||||
|
|
||||||
class CommandResult:
|
class CommandResult:
|
||||||
@@ -33,7 +34,7 @@ class PlayerctlProcess:
|
|||||||
break
|
break
|
||||||
|
|
||||||
asyncio.get_event_loop().create_task(reader(proc.stdout))
|
asyncio.get_event_loop().create_task(reader(proc.stdout))
|
||||||
asyncio.get_event_loop().create_task(printer(proc.stderr))
|
# asyncio.get_event_loop().create_task(printer(proc.stderr))
|
||||||
|
|
||||||
def running(self):
|
def running(self):
|
||||||
return self.proc.returncode is None
|
return self.proc.returncode is None
|
||||||
@@ -69,3 +70,16 @@ class PlayerctlCli:
|
|||||||
stdout, stderr = await proc.communicate()
|
stdout, stderr = await proc.communicate()
|
||||||
await proc.wait()
|
await proc.wait()
|
||||||
return CommandResult(stdout, stderr, proc.returncode)
|
return CommandResult(stdout, stderr, proc.returncode)
|
||||||
|
|
||||||
|
async def list(self, players=[], ignored=[]):
|
||||||
|
args = ['--list-all']
|
||||||
|
|
||||||
|
if players:
|
||||||
|
args.extend(['--player', ','.join(players)])
|
||||||
|
|
||||||
|
if ignored:
|
||||||
|
args.extend(['--ignored-players', ','.join(ignored)])
|
||||||
|
|
||||||
|
cmd = await self.run(join(args))
|
||||||
|
assert cmd.returncode == 0, cmd.stderr
|
||||||
|
return cmd.stdout.splitlines()
|
||||||
|
@@ -62,7 +62,7 @@ async def test_list_names(bus_address):
|
|||||||
async def test_system_list_players(bus_address):
|
async def test_system_list_players(bus_address):
|
||||||
system_players = await setup_mpris('system', system=True)
|
system_players = await setup_mpris('system', system=True)
|
||||||
session_players = await setup_mpris('session1', bus_address=bus_address)
|
session_players = await setup_mpris('session1', bus_address=bus_address)
|
||||||
playerctl = PlayerctlCli(bus_address, debug=False)
|
playerctl = PlayerctlCli(bus_address)
|
||||||
result = await playerctl.run('-l')
|
result = await playerctl.run('-l')
|
||||||
assert result.returncode == 0, result.stdout
|
assert result.returncode == 0, result.stdout
|
||||||
assert result.stdout.split() == ['session1', 'system']
|
assert result.stdout.split() == ['session1', 'system']
|
||||||
|
@@ -21,7 +21,7 @@ async def test_commands(bus_address):
|
|||||||
def get_called(cmd):
|
def get_called(cmd):
|
||||||
return getattr(mpris, f'{cmd.replace("-", "_")}_called')
|
return getattr(mpris, f'{cmd.replace("-", "_")}_called')
|
||||||
|
|
||||||
playerctl = PlayerctlCli(bus_address, debug=True)
|
playerctl = PlayerctlCli(bus_address)
|
||||||
|
|
||||||
results = await asyncio.gather(*(playerctl.run(f'-p commands {cmd}')
|
results = await asyncio.gather(*(playerctl.run(f'-p commands {cmd}')
|
||||||
for cmd in commands + setters))
|
for cmd in commands + setters))
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from .mpris import setup_mpris
|
from .mpris import setup_mpris, setup_playerctld
|
||||||
from .playerctl import PlayerctlCli
|
from .playerctl import PlayerctlCli
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -56,11 +56,10 @@ async def test_selection(bus_address):
|
|||||||
(s1i, s1): (s1i, s1),
|
(s1i, s1): (s1i, s1),
|
||||||
(m6, s1): (s6i, s1, s1i),
|
(m6, s1): (s6i, s1, s1i),
|
||||||
(m4, m6, s3): (s6i, s3),
|
(m4, m6, s3): (s6i, s3),
|
||||||
(any_player, ):
|
(any_player, ): (s1, s1i, s2, s3, s6i),
|
||||||
(s2, s3, s1i, s6i, s1), # order undefined, but consistent
|
|
||||||
(s1, any_player): (s1, s1i, s2, s3, s6i), # s1 first
|
(s1, any_player): (s1, s1i, s2, s3, s6i), # s1 first
|
||||||
(any_player, s1): (s2, s3, s6i, s1i, s1), # s1 last
|
(any_player, s1): (s2, s3, s6i, s1, s1i), # s1 last
|
||||||
(m6, any_player, s2): (s6i, s3, s1i, s1, s2), # s6 first, s2 last
|
(m6, any_player, s2): (s6i, s1, s1i, s3, s2), # s6 first, s2 last
|
||||||
(m6, s1, any_player, s2): (s6i, s1, s1i, s3, s2),
|
(m6, s1, any_player, s2): (s6i, s1, s1i, s3, s2),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +67,66 @@ async def test_selection(bus_address):
|
|||||||
|
|
||||||
for selection, expected in selections.items():
|
for selection, expected in selections.items():
|
||||||
result = await select(*selection)
|
result = await select(*selection)
|
||||||
assert result == expected[0]
|
assert result == expected[0], (selection, expected, result)
|
||||||
result = await select_many(*selection)
|
result = await select_many(*selection)
|
||||||
assert result == expected
|
assert result == expected
|
||||||
|
|
||||||
for mpris in mpris_players:
|
for mpris in mpris_players:
|
||||||
mpris.disconnect()
|
mpris.disconnect()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_daemon_selection(bus_address):
|
||||||
|
playerctld = await setup_playerctld(bus_address=bus_address)
|
||||||
|
playerctl = PlayerctlCli(bus_address)
|
||||||
|
|
||||||
|
def iface_name(player_name):
|
||||||
|
return f'org.mpris.MediaPlayer2.{player_name}'
|
||||||
|
|
||||||
|
def set_players(players):
|
||||||
|
playerctld.player_names = [iface_name(p) for p in players]
|
||||||
|
|
||||||
|
s1 = 'selection1'
|
||||||
|
s1i = 'selection1.i_123'
|
||||||
|
s2 = 'selection2'
|
||||||
|
s2i = 'selection2.i_123'
|
||||||
|
s3 = 'selection3'
|
||||||
|
m4 = 'selection4'
|
||||||
|
m5 = 'selection5'
|
||||||
|
m6 = 'selection6'
|
||||||
|
s6i = 'selection6.i_2'
|
||||||
|
any_player = '%any'
|
||||||
|
|
||||||
|
# selection, players, expected result
|
||||||
|
all_players = [s1, s1i, s2, s3, s6i]
|
||||||
|
tests = [
|
||||||
|
(None, all_players, all_players),
|
||||||
|
(all_players, all_players, all_players),
|
||||||
|
([s2], [s1, s2], [s2]),
|
||||||
|
([s1], [s2, s1i, s1], [s1i, s1]),
|
||||||
|
([s1], [s2, s1, s1i], [s1, s1i]),
|
||||||
|
([s1i, s1], [s1, s1i], [s1i, s1]),
|
||||||
|
([any_player], all_players, all_players),
|
||||||
|
([any_player, s1], [s1, s1i, s2i, s2], [s2i, s2, s1, s1i]),
|
||||||
|
([any_player, s1], [s1, s1i, s2, s2i], [s2, s2i, s1, s1i]),
|
||||||
|
([any_player, s1], [s1i, s1, s2i, s2], [s2i, s2, s1i, s1]),
|
||||||
|
([any_player, s1], [s1i, s1, s2, s2i], [s2, s2i, s1i, s1]),
|
||||||
|
([s2, any_player], [s1, s1i, s2i, s2], [s2i, s2, s1, s1i]),
|
||||||
|
([s2, any_player], [s1, s1i, s2, s2i], [s2, s2i, s1, s1i]),
|
||||||
|
([s2, any_player], [s1i, s1, s2i, s2], [s2i, s2, s1i, s1]),
|
||||||
|
([s2, any_player], [s1i, s1, s2, s2i], [s2, s2i, s1i, s1]),
|
||||||
|
([s2i, any_player], [s1i, s1, s2, s2i], [s2i, s1i, s1, s2]),
|
||||||
|
]
|
||||||
|
|
||||||
|
async def daemon_selection_test(test):
|
||||||
|
selection, players, expected = test
|
||||||
|
set_players(players)
|
||||||
|
result = await playerctl.list(players=selection)
|
||||||
|
assert result == expected, test
|
||||||
|
|
||||||
|
for test in tests:
|
||||||
|
# unfortunately it won't work in parallel because there's only one
|
||||||
|
# playerctld
|
||||||
|
await daemon_selection_test(test)
|
||||||
|
|
||||||
|
playerctld.disconnect()
|
||||||
|
Reference in New Issue
Block a user