From 7784cfad92cfa47bea88d4d8b3f736ee88b2e5e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20Lebrun?= Date: Tue, 26 Apr 2022 13:51:32 +0000 Subject: [PATCH] wpctl: support @DEFAULT_{AUDIO,VIDEO}_{SINK,SOURCE}@ as ID --- src/tools/wpctl.c | 278 +++++++++++++++++++++++++++------------------- 1 file changed, 162 insertions(+), 116 deletions(-) diff --git a/src/tools/wpctl.c b/src/tools/wpctl.c index 81aa936f..96ae2a80 100644 --- a/src/tools/wpctl.c +++ b/src/tools/wpctl.c @@ -34,30 +34,30 @@ struct _WpCtl static struct { union { struct { - guint32 id; + guint64 id; gboolean show_referenced; gboolean show_associated; } inspect; struct { - guint32 id; + guint64 id; } set_default; struct { - guint32 id; + guint64 id; gfloat volume; } set_volume; struct { - guint32 id; + guint64 id; guint mute; } set_mute; struct { - guint32 id; + guint64 id; gint index; } set_profile; struct { - guint32 id; + guint64 id; } clear_default; }; } cmdline; @@ -80,6 +80,76 @@ async_quit (WpCore *core, GAsyncResult *res, WpCtl * self) g_main_loop_quit (self->loop); } +#define DEFAULT_AUDIO_SINK_ID ((guint64)1 << 32) +#define DEFAULT_AUDIO_SOURCE_ID ((guint64)1 << 33) +#define DEFAULT_VIDEO_SOURCE_ID ((guint64)1 << 34) + +static gboolean +parse_id (gboolean allow_def_audio, gboolean allow_def_video, gchar *arg, guint64 *result_id, GError **error) +{ + if (allow_def_audio && (g_strcmp0(arg, "@DEFAULT_SINK@") == 0 || + g_strcmp0(arg, "@DEFAULT_AUDIO_SINK@") == 0)) { + *result_id = DEFAULT_AUDIO_SINK_ID; + } else if (allow_def_audio && (g_strcmp0(arg, "@DEFAULT_SOURCE@") == 0 || + g_strcmp0(arg, "@DEFAULT_AUDIO_SOURCE@") == 0)) { + *result_id = DEFAULT_AUDIO_SOURCE_ID; + } else if (allow_def_video && g_strcmp0(arg, "@DEFAULT_VIDEO_SOURCE@") == 0) { + *result_id = DEFAULT_VIDEO_SOURCE_ID; + } else { + long id = strtol (arg, NULL, 10); + if (id <= 0 || id >= G_MAXUINT32) { + g_set_error (error, wpctl_error_domain_quark(), 0, + "'%s' is not a valid number", arg); + return FALSE; + } + *result_id = id; + } + + return TRUE; +} + +static gboolean +translate_id (WpPlugin *def_nodes_api, guint64 id, guint32 *res, GError **error) +{ + gchar *media_class = NULL; + + if (id == DEFAULT_AUDIO_SINK_ID) { + media_class = "Audio/Sink"; + } else if (id == DEFAULT_AUDIO_SOURCE_ID) { + media_class = "Audio/Source"; + } else if (id == DEFAULT_VIDEO_SOURCE_ID) { + media_class = "Video/Source"; + } else { + /* SPA_ID_INVALID is a special case used by some parse_positional() to + indicate that no specific ID was given. It needs to be checked because + currently SPA_ID_INVALID == G_MAXUINT32. */ + if ((id <= 0 || id >= G_MAXUINT32) && id != SPA_ID_INVALID) { + g_set_error (error, wpctl_error_domain_quark(), 0, + "'%ld' is not a valid ID", id); + return FALSE; + } + + *res = (guint32)id; + return TRUE; + } + + if (!def_nodes_api) { + g_set_error (error, wpctl_error_domain_quark(), 0, + "Default nodes API is not loaded\n"); + return FALSE; + } + + g_signal_emit_by_name (def_nodes_api, "get-default-node", media_class, res); + + if (*res <= 0 || *res >= G_MAXUINT32) { + g_set_error (error, wpctl_error_domain_quark(), 0, + "'%d' is not a valid ID (returned by default-nodes-api)", *res); + return FALSE; + } + + return TRUE; +} + /* status */ static gboolean @@ -384,15 +454,7 @@ inspect_parse_positional (gint argc, gchar ** argv, GError **error) return FALSE; } - long id = strtol (argv[2], NULL, 10); - if (id <= 0 || id >= G_MAXUINT32) { - g_set_error (error, wpctl_error_domain_quark(), 0, - "'%s' is not a valid number", argv[2]); - return FALSE; - } - - cmdline.inspect.id = id; - return TRUE; + return parse_id (true, true, argv[2], &cmdline.inspect.id, error); } static gboolean @@ -562,12 +624,20 @@ static void inspect_run (WpCtl * self) { g_autoptr (WpProxy) proxy = NULL; - guint32 id = cmdline.inspect.id; + g_autoptr (WpPlugin) def_nodes_api = wp_plugin_find (self->core, "default-nodes-api"); + g_autoptr (GError) error = NULL; + guint32 id; + + + if (!translate_id (def_nodes_api, cmdline.inspect.id, &id, &error)) { + fprintf(stderr, "Translate ID error: %s\n\n", error->message); + goto out_err; + } proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY, WP_CONSTRAINT_TYPE_G_PROPERTY, "bound-id", "=u", id, NULL); if (!proxy) { - printf ("Object '%d' not found\n", id); + fprintf (stderr, "Object '%d' not found\n", id); goto out_err; } @@ -591,15 +661,7 @@ set_default_parse_positional (gint argc, gchar ** argv, GError **error) return FALSE; } - long id = strtol (argv[2], NULL, 10); - if (id <= 0 || id >= G_MAXUINT32) { - g_set_error (error, wpctl_error_domain_quark(), 0, - "'%s' is not a valid number", argv[2]); - return FALSE; - } - - cmdline.set_default.id = id; - return TRUE; + return parse_id (false, false, argv[2], &cmdline.set_default.id, error); } static gboolean @@ -621,18 +683,24 @@ set_default_run (WpCtl * self) { g_autoptr (WpPlugin) def_nodes_api = NULL; g_autoptr (WpProxy) proxy = NULL; - guint32 id = cmdline.set_default.id; + guint32 id; + g_autoptr (GError) error = NULL; const gchar *media_class; def_nodes_api = wp_plugin_find (self->core, "default-nodes-api"); if (!def_nodes_api) { - printf ("Default nodes API not loaded\n"); + fprintf (stderr, "Default nodes API not loaded\n"); + goto out; + } + + if (!translate_id (def_nodes_api, cmdline.set_default.id, &id, &error)) { + fprintf(stderr, "Translate ID error: %s\n\n", error->message); goto out; } proxy = wp_object_manager_lookup (self->om, WP_TYPE_NODE, NULL); if (!proxy) { - printf ("Node '%d' not found\n", id); + fprintf (stderr, "Node '%d' not found\n", id); goto out; } @@ -644,14 +712,14 @@ set_default_run (WpCtl * self) const gchar *name = wp_pipewire_object_get_property ( WP_PIPEWIRE_OBJECT (proxy), PW_KEY_NODE_NAME); if (!name) { - printf ("node %u does not have a valid node.name\n", id); + fprintf (stderr, "node %u does not have a valid node.name\n", id); goto out; } g_signal_emit_by_name (def_nodes_api, "set-default-configured-node-name", DEFAULT_NODE_MEDIA_CLASSES[i], name, &res); if (!res) { - printf ("failed to set default node %u (media.class = %s)\n", id, + fprintf (stderr, "failed to set default node %u (media.class = %s)\n", id, media_class); goto out; } @@ -661,7 +729,7 @@ set_default_run (WpCtl * self) } } - printf ("%u is not a device node (media.class = %s)\n", id, media_class); + fprintf (stderr, "%u is not a device node (media.class = %s)\n", id, media_class); out: self->exit_code = 3; @@ -679,30 +747,15 @@ set_volume_parse_positional (gint argc, gchar ** argv, GError **error) return FALSE; } - long id = strtol (argv[2], NULL, 10); - float volume = strtof (argv[3], NULL); - if (id <= 0 || id >= G_MAXUINT32) { - g_set_error (error, wpctl_error_domain_quark(), 0, - "'%s' is not a valid number", argv[2]); - return FALSE; - } - - cmdline.set_volume.id = id; - cmdline.set_volume.volume = volume; - return TRUE; + cmdline.set_volume.volume = strtof (argv[3], NULL); + return parse_id (true, false, argv[2], &cmdline.set_volume.id, error); } static gboolean set_volume_prepare (WpCtl * self, GError ** error) { - wp_object_manager_add_interest (self->om, WP_TYPE_ENDPOINT, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, - "object.id", "=u", cmdline.set_volume.id, - NULL); - wp_object_manager_add_interest (self->om, WP_TYPE_NODE, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, - "object.id", "=u", cmdline.set_volume.id, - NULL); + wp_object_manager_add_interest (self->om, WP_TYPE_ENDPOINT, NULL); + wp_object_manager_add_interest (self->om, WP_TYPE_NODE, NULL); wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY, WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL); return TRUE; @@ -713,22 +766,29 @@ set_volume_run (WpCtl * self) { g_autoptr (WpPipewireObject) proxy = NULL; g_autoptr (WpPlugin) mixer_api = wp_plugin_find (self->core, "mixer-api"); + g_autoptr (WpPlugin) def_nodes_api = wp_plugin_find (self->core, "default-nodes-api"); g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr (GError) error = NULL; GVariant *variant = NULL; gboolean res = FALSE; - guint32 node_id = cmdline.set_volume.id; + guint32 node_id; - proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY, NULL); + if (!translate_id (def_nodes_api, cmdline.set_volume.id, &node_id, &error)) { + fprintf(stderr, "Translate ID error: %s\n\n", error->message); + goto out; + } + + proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=u", node_id, NULL); if (!proxy) { - printf ("Object '%d' not found\n", cmdline.set_volume.id); + fprintf (stderr, "Object '%d' not found\n", node_id); goto out; } if (WP_IS_ENDPOINT (proxy)) { const gchar *str = wp_pipewire_object_get_property (proxy, "node.id"); if (!str) { - printf ("Endpoint '%d' does not have an associated node\n", - cmdline.set_volume.id); + fprintf (stderr, "Endpoint '%d' does not have an associated node\n", node_id); goto out; } node_id = atoi (str); @@ -740,8 +800,7 @@ set_volume_run (WpCtl * self) g_signal_emit_by_name (mixer_api, "set-volume", node_id, variant, &res); if (!res) { - printf ("Object '%d' (node %d) does not support volume\n", - cmdline.set_volume.id, node_id); + fprintf (stderr, "Node %d does not support volume\n", node_id); goto out; } @@ -764,14 +823,6 @@ set_mute_parse_positional (gint argc, gchar ** argv, GError **error) return FALSE; } - long id = strtol (argv[2], NULL, 10); - if (id <= 0 || id >= G_MAXUINT32) { - g_set_error (error, wpctl_error_domain_quark(), 0, - "'%s' is not a valid number", argv[2]); - return FALSE; - } - cmdline.set_mute.id = id; - if (!g_strcmp0 (argv[3], "1")) cmdline.set_mute.mute = 1; else if (!g_strcmp0 (argv[3], "0")) @@ -784,20 +835,14 @@ set_mute_parse_positional (gint argc, gchar ** argv, GError **error) return FALSE; } - return TRUE; + return parse_id (true, false, argv[2], &cmdline.set_mute.id, error); } static gboolean set_mute_prepare (WpCtl * self, GError ** error) { - wp_object_manager_add_interest (self->om, WP_TYPE_ENDPOINT, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, - "object.id", "=u", cmdline.set_mute.id, - NULL); - wp_object_manager_add_interest (self->om, WP_TYPE_NODE, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, - "object.id", "=u", cmdline.set_mute.id, - NULL); + wp_object_manager_add_interest (self->om, WP_TYPE_ENDPOINT, NULL); + wp_object_manager_add_interest (self->om, WP_TYPE_NODE, NULL); wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY, WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL); return TRUE; @@ -808,23 +853,30 @@ set_mute_run (WpCtl * self) { g_autoptr (WpPipewireObject) proxy = NULL; g_autoptr (WpPlugin) mixer_api = wp_plugin_find (self->core, "mixer-api"); + g_autoptr (WpPlugin) def_nodes_api = wp_plugin_find (self->core, "default-nodes-api"); g_auto (GVariantBuilder) b = G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + g_autoptr (GError) error = NULL; GVariant *variant = NULL; gboolean res = FALSE; gboolean mute = FALSE; - guint32 node_id = cmdline.set_mute.id; + guint32 node_id; - proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY, NULL); + if (!translate_id (def_nodes_api, cmdline.set_mute.id, &node_id, &error)) { + fprintf(stderr, "Translate ID error: %s\n\n", error->message); + goto out; + } + + proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=u", node_id, NULL); if (!proxy) { - printf ("Object '%d' not found\n", cmdline.set_mute.id); + fprintf (stderr, "Object '%d' not found\n", node_id); goto out; } if (WP_IS_ENDPOINT (proxy)) { const gchar *str = wp_pipewire_object_get_property (proxy, "node.id"); if (!str) { - printf ("Endpoint '%d' does not have an associated node\n", - cmdline.set_mute.id); + fprintf (stderr, "Endpoint '%d' does not have an associated node\n", node_id); goto out; } node_id = atoi (str); @@ -832,8 +884,7 @@ set_mute_run (WpCtl * self) g_signal_emit_by_name (mixer_api, "get-volume", node_id, &variant); if (!variant) { - printf ("Object '%d' (node %d) does not support mute\n", - cmdline.set_mute.id, node_id); + fprintf (stderr, "Node %d does not support mute\n", node_id); goto out; } @@ -869,26 +920,14 @@ set_profile_parse_positional (gint argc, gchar ** argv, GError **error) return FALSE; } - long id = strtol (argv[2], NULL, 10); - int index = atoi (argv[3]); - if (id < 0 || id >= G_MAXUINT32) { - g_set_error (error, wpctl_error_domain_quark(), 0, - "'%s' is not a valid index", argv[2]); - return FALSE; - } - - cmdline.set_profile.id = id; - cmdline.set_profile.index = index; - return TRUE; + cmdline.set_profile.index = atoi (argv[3]); + return parse_id (true, true, argv[2], &cmdline.set_profile.id, error); } static gboolean set_profile_prepare (WpCtl * self, GError ** error) { - wp_object_manager_add_interest (self->om, WP_TYPE_GLOBAL_PROXY, - WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, - "object.id", "=u", cmdline.set_profile.id, - NULL); + wp_object_manager_add_interest (self->om, WP_TYPE_GLOBAL_PROXY, NULL); wp_object_manager_request_object_features (self->om, WP_TYPE_GLOBAL_PROXY, WP_PIPEWIRE_OBJECT_FEATURES_MINIMAL); return TRUE; @@ -897,11 +936,20 @@ set_profile_prepare (WpCtl * self, GError ** error) static void set_profile_run (WpCtl * self) { + g_autoptr (WpPlugin) def_nodes_api = wp_plugin_find (self->core, "default-nodes-api"); g_autoptr (WpPipewireObject) proxy = NULL; + g_autoptr (GError) error = NULL; + guint32 node_id; - proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY, NULL); + if (!translate_id (def_nodes_api, cmdline.set_profile.id, &node_id, &error)) { + fprintf(stderr, "Translate ID error: %s\n\n", error->message); + goto out; + } + + proxy = wp_object_manager_lookup (self->om, WP_TYPE_GLOBAL_PROXY, + WP_CONSTRAINT_TYPE_PW_GLOBAL_PROPERTY, "object.id", "=u", node_id, NULL); if (!proxy) { - printf ("Object '%d' not found\n", cmdline.set_profile.id); + fprintf (stderr, "Object '%d' not found\n", node_id); goto out; } wp_pipewire_object_set_param (proxy, "Profile", 0, @@ -922,20 +970,12 @@ out: static gboolean clear_default_parse_positional (gint argc, gchar ** argv, GError **error) { - cmdline.clear_default.id = SPA_ID_INVALID; - if (argc >= 3) { - long id = strtol (argv[2], NULL, 10); - if (id < 0 || id >= G_MAXUINT32) { - g_set_error (error, wpctl_error_domain_quark(), 0, - "'%s' is not a valid number", argv[2]); - return FALSE; - } - - cmdline.clear_default.id = id; + return parse_id (true, true, argv[2], &cmdline.clear_default.id, error); + } else { + cmdline.clear_default.id = SPA_ID_INVALID; + return TRUE; } - - return TRUE; } static gboolean @@ -948,12 +988,18 @@ static void clear_default_run (WpCtl * self) { g_autoptr (WpPlugin) def_nodes_api = NULL; - guint32 id = cmdline.clear_default.id; + g_autoptr (GError) error = NULL; + guint32 id; gboolean res = FALSE; def_nodes_api = wp_plugin_find (self->core, "default-nodes-api"); if (!def_nodes_api) { - printf ("Default nodes API not loaded\n"); + fprintf (stderr, "Default nodes API not loaded\n"); + goto out; + } + + if (!translate_id (def_nodes_api, cmdline.clear_default.id, &id, &error)) { + fprintf(stderr, "Translate ID error: %s\n\n", error->message); goto out; } @@ -963,7 +1009,7 @@ clear_default_run (WpCtl * self) g_signal_emit_by_name (def_nodes_api, "set-default-configured-node-name", DEFAULT_NODE_MEDIA_CLASSES[i], NULL, &res); if (!res) { - printf ("failed to clear default configured node (%s)\n", + fprintf (stderr, "failed to clear default configured node (%s)\n", DEFAULT_NODE_MEDIA_CLASSES[i]); goto out; } @@ -973,12 +1019,12 @@ clear_default_run (WpCtl * self) g_signal_emit_by_name (def_nodes_api, "set-default-configured-node-name", DEFAULT_NODE_MEDIA_CLASSES[id], NULL, &res); if (!res) { - printf ("failed to clear default configured node (%s)\n", + fprintf (stderr, "failed to clear default configured node (%s)\n", DEFAULT_NODE_MEDIA_CLASSES[id]); goto out; } } else { - printf ("Id %d is not a valid default node Id\n", id); + fprintf (stderr, "Id %d is not a valid default node Id\n", id); goto out; } }