diff --git a/docs/reference/gio/gdbus.xml b/docs/reference/gio/gdbus.xml index 81682d921..efcec33fe 100644 --- a/docs/reference/gio/gdbus.xml +++ b/docs/reference/gio/gdbus.xml @@ -91,6 +91,20 @@ ARG1 ARG2 + + gdbus + wait + + --system + --session + --address address + + --activate bus_name + + --timeout seconds + + bus_name + gdbus help @@ -147,6 +161,15 @@ not need explicit quotes. + + + + Waits until bus_name is owned by some + process on the bus. If the is specified, + that bus name will be auto-started first. It may be the same as the + bus name being waited for, or different. + + @@ -337,6 +360,38 @@ $ gdbus emit --session --object-path /foo --signal org.bar.Foo "['foo', 'bar', ' $ gdbus emit --session --object-path /bar --signal org.bar.Bar someString --dest :1.42 + + Waiting for a well-known name to be owned on the bus; this will + not auto-start the service: + + +$ gdbus wait --session org.bar.SomeName + + + + Auto-starting then waiting for a well-known name to be owned on the bus: + + +$ gdbus wait --session --activate org.bar.SomeName + + + + Auto-starting a different service, then waiting for a well-known name to be + owned on the bus. This is useful in situations where + SomeName is not directly activatable: + + +$ gdbus wait --session --activate org.bar.PrerequisiteName org.bar.SomeName + + + + Waiting for a well-known name and giving up after 30 seconds. By default, + the timeout is disabled; or set to 0 to disable it: + + +$ gdbus wait --session --timeout 30 org.bar.SomeName + + diff --git a/gio/gdbus-tool.c b/gio/gdbus-tool.c index 7503528ca..3e34b9872 100644 --- a/gio/gdbus-tool.c +++ b/gio/gdbus-tool.c @@ -98,6 +98,7 @@ usage (gint *argc, gchar **argv[], gboolean use_stdout) " monitor Monitor a remote object\n" " call Invoke a method on a remote object\n" " emit Emit a signal\n" + " wait Wait for a bus name to appear\n" "\n" "Use “%s COMMAND --help” to get help on each command.\n"), program_name); @@ -1949,6 +1950,242 @@ handle_monitor (gint *argc, /* ---------------------------------------------------------------------------------------------------- */ +static gboolean opt_wait_activate_set = FALSE; +static gchar *opt_wait_activate_name = NULL; +static gint64 opt_wait_timeout = 0; /* no timeout */ + +typedef enum { + WAIT_STATE_RUNNING, /* waiting to see the service */ + WAIT_STATE_SUCCESS, /* seen it successfully */ + WAIT_STATE_TIMEOUT, /* timed out before seeing it */ +} WaitState; + +static gboolean +opt_wait_activate_cb (const gchar *option_name, + const gchar *value, + gpointer data, + GError **error) +{ + /* @value may be NULL */ + opt_wait_activate_set = TRUE; + opt_wait_activate_name = g_strdup (value); + + return TRUE; +} + +static const GOptionEntry wait_entries[] = +{ + { "activate", 'a', G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK, + opt_wait_activate_cb, + N_("Service to activate before waiting for the other one (well-known name)"), + "[NAME]" }, + { "timeout", 't', 0, G_OPTION_ARG_INT64, &opt_wait_timeout, + N_("Timeout to wait for before exiting with an error (seconds); 0 for " + "no timeout (default)"), "SECS" }, + { NULL } +}; + +static void +wait_name_appeared_cb (GDBusConnection *connection, + const gchar *name, + const gchar *name_owner, + gpointer user_data) +{ + WaitState *wait_state = user_data; + + *wait_state = WAIT_STATE_SUCCESS; +} + +static gboolean +wait_timeout_cb (gpointer user_data) +{ + WaitState *wait_state = user_data; + + *wait_state = WAIT_STATE_TIMEOUT; + + /* Removed in handle_wait(). */ + return G_SOURCE_CONTINUE; +} + +static gboolean +handle_wait (gint *argc, + gchar **argv[], + gboolean request_completion, + const gchar *completion_cur, + const gchar *completion_prev) +{ + gint ret; + GOptionContext *o; + gchar *s; + GError *error; + GDBusConnection *c; + guint watch_id, timer_id = 0, activate_watch_id; + const gchar *activate_service, *wait_service; + WaitState wait_state = WAIT_STATE_RUNNING; + + ret = FALSE; + c = NULL; + + modify_argv0_for_command (argc, argv, "wait"); + + o = g_option_context_new (NULL); + g_option_context_set_help_enabled (o, FALSE); + g_option_context_set_summary (o, _("Wait for a bus name to appear.")); + g_option_context_add_main_entries (o, wait_entries, GETTEXT_PACKAGE); + g_option_context_add_group (o, connection_get_group ()); + + if (!g_option_context_parse (o, argc, argv, NULL)) + { + if (!request_completion) + { + s = g_option_context_get_help (o, FALSE, NULL); + g_printerr ("%s", s); + g_free (s); + goto out; + } + } + + error = NULL; + c = connection_get_dbus_connection (&error); + if (c == NULL) + { + if (request_completion) + { + if (g_strcmp0 (completion_prev, "--address") == 0) + { + g_print ("unix:\n" + "tcp:\n" + "nonce-tcp:\n"); + } + else + { + g_print ("--system \n--session \n--address \n"); + } + } + else + { + g_printerr (_("Error connecting: %s\n"), error->message); + g_error_free (error); + } + goto out; + } + + /* All done with completion now */ + if (request_completion) + goto out; + + /* + * Try and disentangle the command line arguments, with the aim of supporting: + * gdbus wait --session --activate ActivateName WaitName + * gdbus wait --session --activate ActivateAndWaitName + * gdbus wait --activate --session ActivateAndWaitName + * gdbus wait --session WaitName + */ + if (*argc == 2 && opt_wait_activate_set && opt_wait_activate_name != NULL) + { + activate_service = opt_wait_activate_name; + wait_service = (*argv)[1]; + } + else if (*argc == 2 && + opt_wait_activate_set && opt_wait_activate_name == NULL) + { + activate_service = (*argv)[1]; + wait_service = (*argv)[1]; + } + else if (*argc == 2 && !opt_wait_activate_set) + { + activate_service = NULL; /* disabled */ + wait_service = (*argv)[1]; + } + else if (*argc == 1 && + opt_wait_activate_set && opt_wait_activate_name != NULL) + { + activate_service = opt_wait_activate_name; + wait_service = opt_wait_activate_name; + } + else if (*argc == 1 && + opt_wait_activate_set && opt_wait_activate_name == NULL) + { + g_printerr (_("Error: A service to activate for must be specified.\n")); + goto out; + } + else if (*argc == 1 && !opt_wait_activate_set) + { + g_printerr (_("Error: A service to wait for must be specified.\n")); + goto out; + } + else /* if (*argc > 2) */ + { + g_printerr (_("Error: Too many arguments.\n")); + goto out; + } + + if (activate_service != NULL && + (!g_dbus_is_name (activate_service) || + g_dbus_is_unique_name (activate_service))) + { + g_printerr (_("Error: %s is not a valid well-known bus name.\n"), + activate_service); + goto out; + } + + if (!g_dbus_is_name (wait_service) || g_dbus_is_unique_name (wait_service)) + { + g_printerr (_("Error: %s is not a valid well-known bus name.\n"), + wait_service); + goto out; + } + + /* All done with completion now */ + if (request_completion) + goto out; + + /* Start the prerequisite service if needed. */ + if (activate_service != NULL) + { + activate_watch_id = g_bus_watch_name_on_connection (c, activate_service, + G_BUS_NAME_WATCHER_FLAGS_AUTO_START, + NULL, NULL, + NULL, NULL); + } + else + { + activate_watch_id = 0; + } + + /* Wait for the expected name to appear. */ + watch_id = g_bus_watch_name_on_connection (c, + wait_service, + G_BUS_NAME_WATCHER_FLAGS_NONE, + wait_name_appeared_cb, + NULL, &wait_state, NULL); + + /* Safety timeout. */ + if (opt_wait_timeout > 0) + timer_id = g_timeout_add (opt_wait_timeout, wait_timeout_cb, &wait_state); + + while (wait_state == WAIT_STATE_RUNNING) + g_main_context_iteration (NULL, TRUE); + + g_bus_unwatch_name (watch_id); + if (timer_id != 0) + g_source_remove (timer_id); + if (activate_watch_id != 0) + g_bus_unwatch_name (activate_watch_id); + + ret = (wait_state == WAIT_STATE_SUCCESS); + + out: + g_clear_object (&c); + g_option_context_free (o); + g_free (opt_wait_activate_name); + opt_wait_activate_name = NULL; + + return ret; +} + +/* ---------------------------------------------------------------------------------------------------- */ + static gchar * pick_word_at (const gchar *s, gint cursor, @@ -2081,6 +2318,16 @@ main (gint argc, gchar *argv[]) ret = 0; goto out; } + else if (g_strcmp0 (command, "wait") == 0) + { + if (handle_wait (&argc, + &argv, + request_completion, + completion_cur, + completion_prev)) + ret = 0; + goto out; + } else if (g_strcmp0 (command, "complete") == 0 && argc == 4 && !request_completion) { const gchar *completion_line; @@ -2150,7 +2397,7 @@ main (gint argc, gchar *argv[]) { if (request_completion) { - g_print ("help \nemit \ncall \nintrospect \nmonitor \n"); + g_print ("help \nemit \ncall \nintrospect \nmonitor \nwait \n"); ret = 0; goto out; }