diff --git a/src/main.c b/src/main.c index 6d4fef8b..0e4193aa 100644 --- a/src/main.c +++ b/src/main.c @@ -22,36 +22,11 @@ enum WpExitCode }; static gchar * config_file = NULL; -static gchar * exec_script = NULL; -static GVariantBuilder exec_args_b = - G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); - -static gboolean -parse_exec_script_arg (const gchar *option_name, const gchar *value, - gpointer data, GError **error) -{ - g_auto(GStrv) tokens = g_strsplit (value, "=", 2); - if (!tokens[0] || *g_strstrip (tokens[0]) == '\0') { - g_set_error (error, WP_DOMAIN_DAEMON, WP_CODE_INVALID_ARGUMENT, - "invalid script argument '%s'; must be in key=value format", value); - return FALSE; - } - - g_variant_builder_add (&exec_args_b, "{sv}", tokens[0], tokens[1] ? - g_variant_new_string (g_strstrip (tokens[1])) : - g_variant_new_boolean (TRUE)); - return TRUE; -} static GOptionEntry entries[] = { { "config-file", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &config_file, "The configuration file to load components from", NULL }, - { "execute", 'e', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &exec_script, - "Runs WirePlumber in interactive script execution mode, " - "executing the given script", NULL }, - { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, - parse_exec_script_arg, NULL, NULL }, { NULL } }; @@ -67,7 +42,6 @@ struct _WpInitTransition enum { STEP_LOAD_MODULE = WP_TRANSITION_STEP_CUSTOM_START, STEP_LOAD_CONFIG, - STEP_LOAD_SCRIPT, STEP_CONNECT, STEP_ACTIVATE_PLUGINS, STEP_ACTIVATE_SCRIPTS, @@ -87,16 +61,11 @@ wp_init_transition_get_next_step (WpTransition * transition, guint step) { switch (step) { case WP_TRANSITION_STEP_NONE: return STEP_LOAD_MODULE; + case STEP_LOAD_MODULE: return STEP_LOAD_CONFIG; + case STEP_LOAD_CONFIG: return STEP_CONNECT; case STEP_CONNECT: return STEP_ACTIVATE_PLUGINS; case STEP_ACTIVATE_SCRIPTS: return WP_TRANSITION_STEP_NONE; - case STEP_LOAD_MODULE: - return exec_script ? STEP_LOAD_SCRIPT : STEP_LOAD_CONFIG; - - case STEP_LOAD_CONFIG: - case STEP_LOAD_SCRIPT: - return STEP_CONNECT; - case STEP_ACTIVATE_PLUGINS: { WpInitTransition *self = WP_INIT_TRANSITION (transition); if (self->pending_plugins == 0) @@ -160,16 +129,6 @@ wp_init_transition_execute_step (WpTransition * transition, guint step) break; } - case STEP_LOAD_SCRIPT: { - GVariant *args = g_variant_builder_end (&exec_args_b); - if (!wp_core_load_component (core, exec_script, "script/lua", args, &error)) { - wp_transition_return_error (transition, error); - return; - } - wp_transition_advance (transition); - break; - } - case STEP_CONNECT: g_signal_connect_object (core, "connected", G_CALLBACK (wp_transition_advance), transition, G_CONNECT_SWAPPED); @@ -200,7 +159,7 @@ wp_init_transition_execute_step (WpTransition * transition, guint step) wp_info_object (self, "Executing scripts..."); - WpPlugin *p = wp_plugin_find (core, "lua-scripting"); + g_autoptr (WpPlugin) p = wp_plugin_find (core, "lua-scripting"); wp_object_activate (WP_OBJECT (p), WP_PLUGIN_FEATURE_ENABLED, NULL, (GAsyncReadyCallback) on_plugin_activated, self); break; @@ -324,7 +283,7 @@ main (gint argc, gchar **argv) d.loop = g_main_loop_new (NULL, FALSE); d.core = wp_core_new (NULL, wp_properties_new ( PW_KEY_APP_NAME, "WirePlumber", - "wireplumber.interactive", exec_script ? "true" : "false", + "wireplumber.interactive", "false", NULL)); g_signal_connect (d.core, "disconnected", G_CALLBACK (on_disconnected), &d); diff --git a/src/tools/meson.build b/src/tools/meson.build index 233a484d..e4d2e730 100644 --- a/src/tools/meson.build +++ b/src/tools/meson.build @@ -8,3 +8,14 @@ executable('wpctl', install: true, dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep], ) + +executable('wpexec', + 'wpexec.c', + c_args : [ + '-D_GNU_SOURCE', + '-DG_LOG_USE_STRUCTURED', + '-DG_LOG_DOMAIN="wpexec"', + ], + install: true, + dependencies : [gobject_dep, gio_dep, wp_dep, pipewire_dep], +) diff --git a/src/tools/wpexec.c b/src/tools/wpexec.c new file mode 100644 index 00000000..6ab674aa --- /dev/null +++ b/src/tools/wpexec.c @@ -0,0 +1,250 @@ +/* WirePlumber + * + * Copyright © 2019-2021 Collabora Ltd. + * @author George Kiagiadakis + * + * SPDX-License-Identifier: MIT + */ + +#include +#include +#include + +#define WP_DOMAIN_DAEMON (wp_domain_daemon_quark ()) +static G_DEFINE_QUARK (wireplumber-daemon, wp_domain_daemon); + +enum WpExitCode +{ + WP_CODE_OK = 0, + WP_CODE_OPERATION_FAILED, + WP_CODE_INVALID_ARGUMENT, +}; + +static gchar * exec_script = NULL; +static GVariantBuilder exec_args_b = + G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT); + +static gboolean +parse_exec_script_arg (const gchar *option_name, const gchar *value, + gpointer data, GError **error) +{ + /* the first argument is the script */ + if (!exec_script) { + exec_script = g_strdup (value); + return TRUE; + } + + g_auto(GStrv) tokens = g_strsplit (value, "=", 2); + if (!tokens[0] || *g_strstrip (tokens[0]) == '\0') { + g_set_error (error, WP_DOMAIN_DAEMON, WP_CODE_INVALID_ARGUMENT, + "invalid script argument '%s'; must be in key=value format", value); + return FALSE; + } + + g_variant_builder_add (&exec_args_b, "{sv}", tokens[0], tokens[1] ? + g_variant_new_string (g_strstrip (tokens[1])) : + g_variant_new_boolean (TRUE)); + return TRUE; +} + +static GOptionEntry entries[] = +{ + { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, + parse_exec_script_arg, NULL, NULL }, + { NULL } +}; + +/*** WpInitTransition ***/ + +struct _WpInitTransition +{ + WpTransition parent; +}; + +enum { + STEP_LOAD_MODULE = WP_TRANSITION_STEP_CUSTOM_START, + STEP_LOAD_SCRIPT, + STEP_CONNECT, + STEP_ACTIVATE_SCRIPT, +}; + +G_DECLARE_FINAL_TYPE (WpInitTransition, wp_init_transition, + WP, INIT_TRANSITION, WpTransition) +G_DEFINE_TYPE (WpInitTransition, wp_init_transition, WP_TYPE_TRANSITION) + +static void +wp_init_transition_init (WpInitTransition * self) +{ +} + +static guint +wp_init_transition_get_next_step (WpTransition * transition, guint step) +{ + switch (step) { + case WP_TRANSITION_STEP_NONE: return STEP_LOAD_MODULE; + case STEP_LOAD_MODULE: return STEP_LOAD_SCRIPT; + case STEP_LOAD_SCRIPT: return STEP_CONNECT; + case STEP_CONNECT: return STEP_ACTIVATE_SCRIPT; + case STEP_ACTIVATE_SCRIPT: return WP_TRANSITION_STEP_NONE; + default: + g_return_val_if_reached (WP_TRANSITION_STEP_ERROR); + } +} + +static void +on_plugin_activated (WpObject * p, GAsyncResult * res, WpInitTransition *self) +{ + GError *error = NULL; + if (!wp_object_activate_finish (p, res, &error)) { + wp_transition_return_error (WP_TRANSITION (self), error); + return; + } + wp_transition_advance (WP_TRANSITION (self)); +} + +static void +wp_init_transition_execute_step (WpTransition * transition, guint step) +{ + WpInitTransition *self = WP_INIT_TRANSITION (transition); + WpCore *core = wp_transition_get_source_object (transition); + GError *error = NULL; + + switch (step) { + case STEP_LOAD_MODULE: + if (!wp_core_load_component (core, "libwireplumber-module-lua-scripting", + "module", NULL, &error)) { + wp_transition_return_error (transition, error); + return; + } + wp_transition_advance (transition); + break; + + case STEP_LOAD_SCRIPT: { + GVariant *args = g_variant_builder_end (&exec_args_b); + if (!wp_core_load_component (core, exec_script, "script/lua", args, &error)) { + wp_transition_return_error (transition, error); + return; + } + wp_transition_advance (transition); + break; + } + + case STEP_CONNECT: + g_signal_connect_object (core, "connected", + G_CALLBACK (wp_transition_advance), transition, G_CONNECT_SWAPPED); + + if (!wp_core_connect (core)) { + wp_transition_return_error (transition, g_error_new (WP_DOMAIN_DAEMON, + WP_CODE_OPERATION_FAILED, "Failed to connect to PipeWire")); + return; + } + break; + + case STEP_ACTIVATE_SCRIPT: { + g_autoptr (WpPlugin) p = wp_plugin_find (core, "lua-scripting"); + wp_object_activate (WP_OBJECT (p), WP_PLUGIN_FEATURE_ENABLED, NULL, + (GAsyncReadyCallback) on_plugin_activated, self); + break; + } + + case WP_TRANSITION_STEP_ERROR: + break; + + default: + g_assert_not_reached (); + } +} + +static void +wp_init_transition_class_init (WpInitTransitionClass * klass) +{ + WpTransitionClass * transition_class = (WpTransitionClass *) klass; + + transition_class->get_next_step = wp_init_transition_get_next_step; + transition_class->execute_step = wp_init_transition_execute_step; +} + +/*** WpExec ***/ + +typedef struct +{ + WpCore *core; + GMainLoop *loop; + gint exit_code; +} WpExec; + +static void +wpexec_clear (WpExec * self) +{ + g_clear_pointer (&self->loop, g_main_loop_unref); + g_clear_object (&self->core); +} + +G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC (WpExec, wpexec_clear) + +static gboolean +signal_handler (gpointer data) +{ + WpExec *d = data; + g_main_loop_quit (d->loop); + return G_SOURCE_REMOVE; +} + +static gboolean +init_start (WpTransition * transition) +{ + wp_transition_advance (transition); + return G_SOURCE_REMOVE; +} + +static void +init_done (WpCore * core, GAsyncResult * res, WpExec * d) +{ + g_autoptr (GError) error = NULL; + if (!wp_transition_finish (res, &error)) { + wp_message ("%s", error->message); + d->exit_code = WP_CODE_OPERATION_FAILED; + g_main_loop_quit (d->loop); + } +} + +gint +main (gint argc, gchar **argv) +{ + g_auto (WpExec) d = {0}; + g_autoptr (GOptionContext) context = NULL; + g_autoptr (GError) error = NULL; + + wp_init (WP_INIT_ALL); + + context = g_option_context_new ("- WirePlumber script interpreter"); + g_option_context_add_main_entries (context, entries, NULL); + if (!g_option_context_parse (context, &argc, &argv, &error)) { + wp_message ("%s", error->message); + return WP_CODE_INVALID_ARGUMENT; + } + + /* init wireplumber core */ + d.loop = g_main_loop_new (NULL, FALSE); + d.core = wp_core_new (NULL, wp_properties_new ( + PW_KEY_APP_NAME, "wpexec", + "wireplumber.interactive", "true", + NULL)); + g_signal_connect_swapped (d.core, "disconnected", + G_CALLBACK (g_main_loop_quit), d.loop); + + /* watch for exit signals */ + g_unix_signal_add (SIGINT, signal_handler, &d); + g_unix_signal_add (SIGTERM, signal_handler, &d); + g_unix_signal_add (SIGHUP, signal_handler, &d); + + /* initialization transition */ + g_idle_add ((GSourceFunc) init_start, + wp_transition_new (wp_init_transition_get_type (), d.core, + NULL, (GAsyncReadyCallback) init_done, &d)); + + /* run */ + g_main_loop_run (d.loop); + wp_core_disconnect (d.core); + return d.exit_code; +} diff --git a/tests/examples/bt-profile-switch.lua b/tests/examples/bt-profile-switch.lua index ee9c88ef..776f0dd2 100755 --- a/tests/examples/bt-profile-switch.lua +++ b/tests/examples/bt-profile-switch.lua @@ -1,4 +1,4 @@ -#!/usr/bin/wireplumber -e +#!/usr/bin/wpexec -- -- WirePlumber -- diff --git a/tests/examples/interactive.lua b/tests/examples/interactive.lua index 58c5595a..4d2e1dc5 100755 --- a/tests/examples/interactive.lua +++ b/tests/examples/interactive.lua @@ -1,4 +1,4 @@ -#!/usr/bin/wireplumber -e +#!/usr/bin/wpexec -- -- WirePlumber -- @@ -10,7 +10,7 @@ -- This is an example of an interactive script -- -- Execute with: --- wireplumber -e ./interactive.lua option1=value1 option2=value2 ... +-- wpexec ./interactive.lua option1=value1 option2=value2 ... -- or: -- ./interactive.lua option1=value1 option2=value2 -----------------------------------------------------------------------------