blob: 63c7ec9dc04fa578c8af21a3ff37cf35e36daf77 [file] [log] [blame]
/* GDBus - GLib D-Bus Library
*
* Copyright (C) 2008-2010 Red Hat, Inc.
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
*
* Author: David Zeuthen <davidz@redhat.com>
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <locale.h>
#include <gio/gio.h>
#ifdef G_OS_UNIX
#include <gio/gunixfdlist.h>
#endif
#include <gi18n.h>
#ifdef G_OS_WIN32
#include "glib/glib-private.h"
#include "gdbusprivate.h"
#endif
/* ---------------------------------------------------------------------------------------------------- */
/* Escape values for console colors */
#define UNDERLINE "\033[4m"
#define BLUE "\033[34m"
#define CYAN "\033[36m"
#define GREEN "\033[32m"
#define MAGENTA "\033[35m"
#define RED "\033[31m"
#define YELLOW "\033[33m"
/* ---------------------------------------------------------------------------------------------------- */
G_GNUC_UNUSED static void completion_debug (const gchar *format, ...);
/* Uncomment to get debug traces in /tmp/gdbus-completion-debug.txt (nice
* to not have it interfere with stdout/stderr)
*/
#if 0
G_GNUC_UNUSED static void
completion_debug (const gchar *format, ...)
{
va_list var_args;
gchar *s;
static FILE *f = NULL;
va_start (var_args, format);
s = g_strdup_vprintf (format, var_args);
if (f == NULL)
{
f = fopen ("/tmp/gdbus-completion-debug.txt", "a+");
}
fprintf (f, "%s\n", s);
g_free (s);
}
#else
static void
completion_debug (const gchar *format, ...)
{
}
#endif
/* ---------------------------------------------------------------------------------------------------- */
static void
remove_arg (gint num, gint *argc, gchar **argv[])
{
gint n;
g_assert (num <= (*argc));
for (n = num; (*argv)[n] != NULL; n++)
(*argv)[n] = (*argv)[n+1];
(*argv)[n] = NULL;
(*argc) = (*argc) - 1;
}
static void
usage (gint *argc, gchar **argv[], gboolean use_stdout)
{
GOptionContext *o;
gchar *s;
gchar *program_name;
o = g_option_context_new (_("COMMAND"));
g_option_context_set_help_enabled (o, FALSE);
/* Ignore parsing result */
g_option_context_parse (o, argc, argv, NULL);
program_name = (*argc > 0) ? g_path_get_basename ((*argv)[0]) : g_strdup ("gdbus-tool");
s = g_strdup_printf (_("Commands:\n"
" help Shows this information\n"
" introspect Introspect a remote object\n"
" 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);
g_free (program_name);
g_option_context_set_description (o, s);
g_free (s);
s = g_option_context_get_help (o, FALSE, NULL);
if (use_stdout)
g_print ("%s", s);
else
g_printerr ("%s", s);
g_free (s);
g_option_context_free (o);
}
static void
modify_argv0_for_command (gint *argc, gchar **argv[], const gchar *command)
{
gchar *s;
gchar *program_name;
/* TODO:
* 1. get a g_set_prgname() ?; or
* 2. save old argv[0] and restore later
*/
g_assert (*argc > 1);
g_assert (g_strcmp0 ((*argv)[1], command) == 0);
remove_arg (1, argc, argv);
program_name = g_path_get_basename ((*argv)[0]);
s = g_strdup_printf ("%s %s", program_name, command);
(*argv)[0] = s;
g_free (program_name);
}
static GOptionContext *
command_option_context_new (const gchar *parameter_string,
const gchar *summary,
const GOptionEntry *entries,
gboolean request_completion)
{
GOptionContext *o = NULL;
o = g_option_context_new (parameter_string);
if (request_completion)
g_option_context_set_ignore_unknown_options (o, TRUE);
g_option_context_set_help_enabled (o, FALSE);
g_option_context_set_summary (o, summary);
g_option_context_add_main_entries (o, entries, GETTEXT_PACKAGE);
return g_steal_pointer (&o);
}
/* ---------------------------------------------------------------------------------------------------- */
static void
print_methods_and_signals (GDBusConnection *c,
const gchar *name,
const gchar *path,
gboolean print_methods,
gboolean print_signals)
{
GVariant *result;
GError *error;
const gchar *xml_data;
GDBusNodeInfo *node;
guint n;
guint m;
error = NULL;
result = g_dbus_connection_call_sync (c,
name,
path,
"org.freedesktop.DBus.Introspectable",
"Introspect",
NULL,
G_VARIANT_TYPE ("(s)"),
G_DBUS_CALL_FLAGS_NONE,
3000, /* 3 secs */
NULL,
&error);
if (result == NULL)
{
g_printerr (_("Error: %s\n"), error->message);
g_error_free (error);
goto out;
}
g_variant_get (result, "(&s)", &xml_data);
error = NULL;
node = g_dbus_node_info_new_for_xml (xml_data, &error);
g_variant_unref (result);
if (node == NULL)
{
g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
g_error_free (error);
goto out;
}
for (n = 0; node->interfaces != NULL && node->interfaces[n] != NULL; n++)
{
const GDBusInterfaceInfo *iface = node->interfaces[n];
for (m = 0; print_methods && iface->methods != NULL && iface->methods[m] != NULL; m++)
{
const GDBusMethodInfo *method = iface->methods[m];
g_print ("%s.%s \n", iface->name, method->name);
}
for (m = 0; print_signals && iface->signals != NULL && iface->signals[m] != NULL; m++)
{
const GDBusSignalInfo *signal = iface->signals[m];
g_print ("%s.%s \n", iface->name, signal->name);
}
}
g_dbus_node_info_unref (node);
out:
;
}
static void
print_paths (GDBusConnection *c,
const gchar *name,
const gchar *path)
{
GVariant *result;
GError *error;
const gchar *xml_data;
GDBusNodeInfo *node;
guint n;
if (!g_dbus_is_name (name))
{
g_printerr (_("Error: %s is not a valid name\n"), name);
goto out;
}
if (!g_variant_is_object_path (path))
{
g_printerr (_("Error: %s is not a valid object path\n"), path);
goto out;
}
error = NULL;
result = g_dbus_connection_call_sync (c,
name,
path,
"org.freedesktop.DBus.Introspectable",
"Introspect",
NULL,
G_VARIANT_TYPE ("(s)"),
G_DBUS_CALL_FLAGS_NONE,
3000, /* 3 secs */
NULL,
&error);
if (result == NULL)
{
g_printerr (_("Error: %s\n"), error->message);
g_error_free (error);
goto out;
}
g_variant_get (result, "(&s)", &xml_data);
//g_printerr ("xml='%s'", xml_data);
error = NULL;
node = g_dbus_node_info_new_for_xml (xml_data, &error);
g_variant_unref (result);
if (node == NULL)
{
g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
g_error_free (error);
goto out;
}
//g_printerr ("bar '%s'\n", path);
if (node->interfaces != NULL)
g_print ("%s \n", path);
for (n = 0; node->nodes != NULL && node->nodes[n] != NULL; n++)
{
gchar *s;
//g_printerr ("foo '%s'\n", node->nodes[n].path);
if (g_strcmp0 (path, "/") == 0)
s = g_strdup_printf ("/%s", node->nodes[n]->path);
else
s = g_strdup_printf ("%s/%s", path, node->nodes[n]->path);
print_paths (c, name, s);
g_free (s);
}
g_dbus_node_info_unref (node);
out:
;
}
static void
print_names (GDBusConnection *c,
gboolean include_unique_names)
{
GVariant *result;
GError *error;
GVariantIter *iter;
gchar *str;
GHashTable *name_set;
GPtrArray *keys;
name_set = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
error = NULL;
result = g_dbus_connection_call_sync (c,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"ListNames",
NULL,
G_VARIANT_TYPE ("(as)"),
G_DBUS_CALL_FLAGS_NONE,
3000, /* 3 secs */
NULL,
&error);
if (result == NULL)
{
g_printerr (_("Error: %s\n"), error->message);
g_error_free (error);
goto out;
}
g_variant_get (result, "(as)", &iter);
while (g_variant_iter_loop (iter, "s", &str))
g_hash_table_add (name_set, g_strdup (str));
g_variant_iter_free (iter);
g_variant_unref (result);
error = NULL;
result = g_dbus_connection_call_sync (c,
"org.freedesktop.DBus",
"/org/freedesktop/DBus",
"org.freedesktop.DBus",
"ListActivatableNames",
NULL,
G_VARIANT_TYPE ("(as)"),
G_DBUS_CALL_FLAGS_NONE,
3000, /* 3 secs */
NULL,
&error);
if (result == NULL)
{
g_printerr (_("Error: %s\n"), error->message);
g_error_free (error);
goto out;
}
g_variant_get (result, "(as)", &iter);
while (g_variant_iter_loop (iter, "s", &str))
g_hash_table_add (name_set, g_strdup (str));
g_variant_iter_free (iter);
g_variant_unref (result);
keys = g_hash_table_steal_all_keys (name_set);
g_ptr_array_sort_values (keys, (GCompareFunc) g_strcmp0);
for (guint i = 0; i < keys->len; ++i)
{
const gchar *name = g_ptr_array_index (keys, i);
if (!include_unique_names && g_str_has_prefix (name, ":"))
continue;
g_print ("%s \n", name);
}
g_clear_pointer (&keys, g_ptr_array_unref);
out:
g_hash_table_unref (name_set);
}
/* ---------------------------------------------------------------------------------------------------- */
static gboolean opt_connection_system = FALSE;
static gboolean opt_connection_session = FALSE;
static gchar *opt_connection_address = NULL;
static const GOptionEntry connection_entries[] =
{
{ "system", 'y', 0, G_OPTION_ARG_NONE, &opt_connection_system, N_("Connect to the system bus"), NULL},
{ "session", 'e', 0, G_OPTION_ARG_NONE, &opt_connection_session, N_("Connect to the session bus"), NULL},
{ "address", 'a', 0, G_OPTION_ARG_STRING, &opt_connection_address, N_("Connect to given D-Bus address"), N_("ADDRESS") },
G_OPTION_ENTRY_NULL
};
static GOptionGroup *
connection_get_group (void)
{
static GOptionGroup *g;
g = g_option_group_new ("connection",
N_("Connection Endpoint Options:"),
N_("Options specifying the connection endpoint"),
NULL,
NULL);
g_option_group_set_translation_domain (g, GETTEXT_PACKAGE);
g_option_group_add_entries (g, connection_entries);
return g;
}
static GDBusConnection *
connection_get_dbus_connection (gboolean require_message_bus,
GError **error)
{
GDBusConnection *c;
c = NULL;
/* First, ensure we have exactly one connect */
if (!opt_connection_system && !opt_connection_session && opt_connection_address == NULL)
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("No connection endpoint specified"));
goto out;
}
else if ((opt_connection_system && (opt_connection_session || opt_connection_address != NULL)) ||
(opt_connection_session && (opt_connection_system || opt_connection_address != NULL)) ||
(opt_connection_address != NULL && (opt_connection_system || opt_connection_session)))
{
g_set_error (error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
_("Multiple connection endpoints specified"));
goto out;
}
if (opt_connection_system)
{
c = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, error);
}
else if (opt_connection_session)
{
c = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, error);
}
else if (opt_connection_address != NULL)
{
GDBusConnectionFlags flags = G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT;
if (require_message_bus)
flags |= G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION;
c = g_dbus_connection_new_for_address_sync (opt_connection_address,
flags,
NULL, /* GDBusAuthObserver */
NULL, /* GCancellable */
error);
}
out:
return c;
}
/* ---------------------------------------------------------------------------------------------------- */
static GPtrArray *
call_helper_get_method_in_signature (GDBusConnection *c,
const gchar *dest,
const gchar *path,
const gchar *interface_name,
const gchar *method_name,
GError **error)
{
GPtrArray *ret;
GVariant *result;
GDBusNodeInfo *node_info;
const gchar *xml_data;
GDBusInterfaceInfo *interface_info;
GDBusMethodInfo *method_info;
guint n;
ret = NULL;
result = NULL;
node_info = NULL;
result = g_dbus_connection_call_sync (c,
dest,
path,
"org.freedesktop.DBus.Introspectable",
"Introspect",
NULL,
G_VARIANT_TYPE ("(s)"),
G_DBUS_CALL_FLAGS_NONE,
3000, /* 3 secs */
NULL,
error);
if (result == NULL)
goto out;
g_variant_get (result, "(&s)", &xml_data);
node_info = g_dbus_node_info_new_for_xml (xml_data, error);
if (node_info == NULL)
goto out;
interface_info = g_dbus_node_info_lookup_interface (node_info, interface_name);
if (interface_info == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Warning: According to introspection data, interface “%s” does not exist\n"),
interface_name);
goto out;
}
method_info = g_dbus_interface_info_lookup_method (interface_info, method_name);
if (method_info == NULL)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
_("Warning: According to introspection data, method “%s” does not exist on interface “%s”\n"),
method_name,
interface_name);
goto out;
}
ret = g_ptr_array_new_with_free_func ((GDestroyNotify) g_variant_type_free);
for (n = 0; method_info->in_args != NULL && method_info->in_args[n] != NULL; n++)
{
g_ptr_array_add (ret, g_variant_type_new (method_info->in_args[n]->signature));
}
out:
if (node_info != NULL)
g_dbus_node_info_unref (node_info);
if (result != NULL)
g_variant_unref (result);
return ret;
}
/* ---------------------------------------------------------------------------------------------------- */
static GVariant *
_g_variant_parse_me_harder (GVariantType *type,
const gchar *given_str,
GError **error)
{
GVariant *value;
gchar *s;
guint n;
GString *str;
str = g_string_new ("\"");
for (n = 0; given_str[n] != '\0'; n++)
{
if (G_UNLIKELY (given_str[n] == '\"'))
g_string_append (str, "\\\"");
else
g_string_append_c (str, given_str[n]);
}
g_string_append_c (str, '"');
s = g_string_free (str, FALSE);
value = g_variant_parse (type,
s,
NULL,
NULL,
error);
g_free (s);
return value;
}
/* ---------------------------------------------------------------------------------------------------- */
static gchar *opt_emit_dest = NULL;
static gchar *opt_emit_object_path = NULL;
static gchar *opt_emit_signal = NULL;
static const GOptionEntry emit_entries[] =
{
{ "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_emit_dest, N_("Optional destination for signal (unique name)"), NULL},
{ "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_emit_object_path, N_("Object path to emit signal on"), NULL},
{ "signal", 's', 0, G_OPTION_ARG_STRING, &opt_emit_signal, N_("Signal and interface name"), NULL},
G_OPTION_ENTRY_NULL
};
static gboolean
handle_emit (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;
GVariant *parameters;
gchar *interface_name;
gchar *signal_name;
GVariantBuilder builder;
gboolean skip_dashes;
guint parm;
guint n;
gboolean complete_names, complete_paths, complete_signals;
ret = FALSE;
c = NULL;
parameters = NULL;
interface_name = NULL;
signal_name = NULL;
modify_argv0_for_command (argc, argv, "emit");
o = command_option_context_new (NULL, _("Emit a signal."),
emit_entries, request_completion);
g_option_context_add_group (o, connection_get_group ());
complete_names = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0)
{
complete_names = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
complete_paths = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0)
{
complete_paths = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
complete_signals = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--signal") == 0)
{
complete_signals = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
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 ((opt_emit_dest != NULL), &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;
}
/* validate and complete destination (bus name) */
if (complete_names)
{
print_names (c, FALSE);
goto out;
}
if (request_completion && opt_emit_dest != NULL && g_strcmp0 ("--dest", completion_prev) == 0)
{
print_names (c, g_str_has_prefix (opt_emit_dest, ":"));
goto out;
}
if (!request_completion && opt_emit_dest != NULL && !g_dbus_is_unique_name (opt_emit_dest))
{
g_printerr (_("Error: %s is not a valid unique bus name.\n"), opt_emit_dest);
goto out;
}
if (opt_emit_dest == NULL && opt_emit_object_path == NULL && request_completion)
{
g_print ("--dest \n");
}
/* validate and complete object path */
if (opt_emit_dest != NULL && complete_paths)
{
print_paths (c, opt_emit_dest, "/");
goto out;
}
if (opt_emit_object_path == NULL)
{
if (request_completion)
g_print ("--object-path \n");
else
g_printerr (_("Error: Object path is not specified\n"));
goto out;
}
if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0)
{
if (opt_emit_dest != NULL)
{
gchar *p;
s = g_strdup (opt_emit_object_path);
p = strrchr (s, '/');
if (p != NULL)
{
if (p == s)
p++;
*p = '\0';
}
print_paths (c, opt_emit_dest, s);
g_free (s);
}
goto out;
}
if (!request_completion && !g_variant_is_object_path (opt_emit_object_path))
{
g_printerr (_("Error: %s is not a valid object path\n"), opt_emit_object_path);
goto out;
}
/* validate and complete signal (interface + signal name) */
if (opt_emit_dest != NULL && opt_emit_object_path != NULL && complete_signals)
{
print_methods_and_signals (c, opt_emit_dest, opt_emit_object_path, FALSE, TRUE);
goto out;
}
if (opt_emit_signal == NULL)
{
/* don't keep repeatedly completing --signal */
if (request_completion)
{
if (g_strcmp0 ("--signal", completion_prev) != 0)
g_print ("--signal \n");
}
else
{
g_printerr (_("Error: Signal name is not specified\n"));
}
goto out;
}
if (request_completion && opt_emit_dest != NULL && opt_emit_object_path != NULL &&
g_strcmp0 ("--signal", completion_prev) == 0)
{
print_methods_and_signals (c, opt_emit_dest, opt_emit_object_path, FALSE, TRUE);
goto out;
}
s = strrchr (opt_emit_signal, '.');
if (!request_completion && s == NULL)
{
g_printerr (_("Error: Signal name “%s” is invalid\n"), opt_emit_signal);
goto out;
}
signal_name = g_strdup (s + 1);
interface_name = g_strndup (opt_emit_signal, s - opt_emit_signal);
/* All done with completion now */
if (request_completion)
goto out;
if (!g_dbus_is_interface_name (interface_name))
{
g_printerr (_("Error: %s is not a valid interface name\n"), interface_name);
goto out;
}
if (!g_dbus_is_member_name (signal_name))
{
g_printerr (_("Error: %s is not a valid member name\n"), signal_name);
goto out;
}
/* Read parameters */
g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
skip_dashes = TRUE;
parm = 0;
for (n = 1; n < (guint) *argc; n++)
{
GVariant *value;
/* Under certain conditions, g_option_context_parse returns the "--"
itself (setting off unparsed arguments), too: */
if (skip_dashes && g_strcmp0 ((*argv)[n], "--") == 0)
{
skip_dashes = FALSE;
continue;
}
error = NULL;
value = g_variant_parse (NULL,
(*argv)[n],
NULL,
NULL,
&error);
if (value == NULL)
{
gchar *context;
context = g_variant_parse_error_print_context (error, (*argv)[n]);
g_error_free (error);
error = NULL;
value = _g_variant_parse_me_harder (NULL, (*argv)[n], &error);
if (value == NULL)
{
/* Use the original non-"parse-me-harder" error */
g_printerr (_("Error parsing parameter %d: %s\n"),
parm + 1,
context);
g_error_free (error);
g_free (context);
g_variant_builder_clear (&builder);
goto out;
}
g_free (context);
}
g_variant_builder_add_value (&builder, value);
++parm;
}
parameters = g_variant_builder_end (&builder);
if (parameters != NULL)
parameters = g_variant_ref_sink (parameters);
if (!g_dbus_connection_emit_signal (c,
opt_emit_dest,
opt_emit_object_path,
interface_name,
signal_name,
parameters,
&error))
{
g_printerr (_("Error: %s\n"), error->message);
g_error_free (error);
goto out;
}
if (!g_dbus_connection_flush_sync (c, NULL, &error))
{
g_printerr (_("Error flushing connection: %s\n"), error->message);
g_error_free (error);
goto out;
}
ret = TRUE;
out:
if (c != NULL)
g_object_unref (c);
if (parameters != NULL)
g_variant_unref (parameters);
g_free (interface_name);
g_free (signal_name);
g_option_context_free (o);
return ret;
}
/* ---------------------------------------------------------------------------------------------------- */
static gchar *opt_call_dest = NULL;
static gchar *opt_call_object_path = NULL;
static gchar *opt_call_method = NULL;
static gint opt_call_timeout = -1;
static gboolean opt_call_interactive = FALSE;
static const GOptionEntry call_entries[] =
{
{ "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_call_dest, N_("Destination name to invoke method on"), NULL},
{ "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_call_object_path, N_("Object path to invoke method on"), NULL},
{ "method", 'm', 0, G_OPTION_ARG_STRING, &opt_call_method, N_("Method and interface name"), NULL},
{ "timeout", 't', 0, G_OPTION_ARG_INT, &opt_call_timeout, N_("Timeout in seconds"), NULL},
{ "interactive", 'i', 0, G_OPTION_ARG_NONE, &opt_call_interactive, N_("Allow interactive authorization"), NULL},
G_OPTION_ENTRY_NULL
};
static gboolean
handle_call (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;
GVariant *parameters;
gchar *interface_name;
gchar *method_name;
GVariant *result;
GPtrArray *in_signature_types;
#ifdef G_OS_UNIX
GUnixFDList *fd_list;
gint fd_id;
#endif
gboolean complete_names;
gboolean complete_paths;
gboolean complete_methods;
GVariantBuilder builder;
gboolean skip_dashes;
guint parm;
guint n;
GDBusCallFlags flags;
ret = FALSE;
c = NULL;
parameters = NULL;
interface_name = NULL;
method_name = NULL;
result = NULL;
in_signature_types = NULL;
#ifdef G_OS_UNIX
fd_list = NULL;
#endif
modify_argv0_for_command (argc, argv, "call");
o = command_option_context_new (NULL, _("Invoke a method on a remote object."),
call_entries, request_completion);
g_option_context_add_group (o, connection_get_group ());
complete_names = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0)
{
complete_names = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
complete_paths = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0)
{
complete_paths = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
complete_methods = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--method") == 0)
{
complete_methods = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
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 (TRUE, &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;
}
/* validate and complete destination (bus name) */
if (complete_names)
{
print_names (c, FALSE);
goto out;
}
if (opt_call_dest == NULL)
{
if (request_completion)
g_print ("--dest \n");
else
g_printerr (_("Error: Destination is not specified\n"));
goto out;
}
if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0)
{
print_names (c, g_str_has_prefix (opt_call_dest, ":"));
goto out;
}
if (!request_completion && !g_dbus_is_name (opt_call_dest))
{
g_printerr (_("Error: %s is not a valid bus name\n"), opt_call_dest);
goto out;
}
/* validate and complete object path */
if (complete_paths)
{
print_paths (c, opt_call_dest, "/");
goto out;
}
if (opt_call_object_path == NULL)
{
if (request_completion)
g_print ("--object-path \n");
else
g_printerr (_("Error: Object path is not specified\n"));
goto out;
}
if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0)
{
gchar *p;
s = g_strdup (opt_call_object_path);
p = strrchr (s, '/');
if (p != NULL)
{
if (p == s)
p++;
*p = '\0';
}
print_paths (c, opt_call_dest, s);
g_free (s);
goto out;
}
if (!request_completion && !g_variant_is_object_path (opt_call_object_path))
{
g_printerr (_("Error: %s is not a valid object path\n"), opt_call_object_path);
goto out;
}
/* validate and complete method (interface + method name) */
if (complete_methods)
{
print_methods_and_signals (c, opt_call_dest, opt_call_object_path, TRUE, FALSE);
goto out;
}
if (opt_call_method == NULL)
{
if (request_completion)
g_print ("--method \n");
else
g_printerr (_("Error: Method name is not specified\n"));
goto out;
}
if (request_completion && g_strcmp0 ("--method", completion_prev) == 0)
{
print_methods_and_signals (c, opt_call_dest, opt_call_object_path, TRUE, FALSE);
goto out;
}
s = strrchr (opt_call_method, '.');
if (!request_completion && s == NULL)
{
g_printerr (_("Error: Method name “%s” is invalid\n"), opt_call_method);
goto out;
}
method_name = g_strdup (s + 1);
interface_name = g_strndup (opt_call_method, s - opt_call_method);
/* All done with completion now */
if (request_completion)
goto out;
/* Introspect, for easy conversion - it's not fatal if we can't do this */
in_signature_types = call_helper_get_method_in_signature (c,
opt_call_dest,
opt_call_object_path,
interface_name,
method_name,
&error);
if (in_signature_types == NULL)
{
//g_printerr ("Error getting introspection data: %s\n", error->message);
g_error_free (error);
error = NULL;
}
/* Read parameters */
g_variant_builder_init (&builder, G_VARIANT_TYPE_TUPLE);
skip_dashes = TRUE;
parm = 0;
for (n = 1; n < (guint) *argc; n++)
{
GVariant *value;
GVariantType *type;
/* Under certain conditions, g_option_context_parse returns the "--"
itself (setting off unparsed arguments), too: */
if (skip_dashes && g_strcmp0 ((*argv)[n], "--") == 0)
{
skip_dashes = FALSE;
continue;
}
type = NULL;
if (in_signature_types != NULL)
{
if (parm >= in_signature_types->len)
{
/* Only warn for the first param */
if (parm == in_signature_types->len)
{
g_printerr ("Warning: Introspection data indicates %d parameters but more was passed\n",
in_signature_types->len);
}
}
else
{
type = in_signature_types->pdata[parm];
}
}
error = NULL;
value = g_variant_parse (type,
(*argv)[n],
NULL,
NULL,
&error);
if (value == NULL)
{
gchar *context;
context = g_variant_parse_error_print_context (error, (*argv)[n]);
g_error_free (error);
error = NULL;
value = _g_variant_parse_me_harder (type, (*argv)[n], &error);
if (value == NULL)
{
if (type != NULL)
{
s = g_variant_type_dup_string (type);
g_printerr (_("Error parsing parameter %d of type “%s”: %s\n"),
parm + 1,
s,
context);
g_free (s);
}
else
{
g_printerr (_("Error parsing parameter %d: %s\n"),
parm + 1,
context);
}
g_error_free (error);
g_variant_builder_clear (&builder);
g_free (context);
goto out;
}
g_free (context);
}
#ifdef G_OS_UNIX
if (g_variant_is_of_type (value, G_VARIANT_TYPE_HANDLE))
{
if (!fd_list)
fd_list = g_unix_fd_list_new ();
if ((fd_id = g_unix_fd_list_append (fd_list, g_variant_get_handle (value), &error)) == -1)
{
g_printerr (_("Error adding handle %d: %s\n"),
g_variant_get_handle (value), error->message);
g_variant_builder_clear (&builder);
g_error_free (error);
goto out;
}
g_variant_unref (value);
value = g_variant_new_handle (fd_id);
}
#endif
g_variant_builder_add_value (&builder, value);
++parm;
}
parameters = g_variant_builder_end (&builder);
if (parameters != NULL)
parameters = g_variant_ref_sink (parameters);
flags = G_DBUS_CALL_FLAGS_NONE;
if (opt_call_interactive)
flags |= G_DBUS_CALL_FLAGS_ALLOW_INTERACTIVE_AUTHORIZATION;
#ifdef G_OS_UNIX
result = g_dbus_connection_call_with_unix_fd_list_sync (c,
opt_call_dest,
opt_call_object_path,
interface_name,
method_name,
parameters,
NULL,
flags,
opt_call_timeout > 0 ? opt_call_timeout * 1000 : opt_call_timeout,
fd_list,
NULL,
NULL,
&error);
#else
result = g_dbus_connection_call_sync (c,
opt_call_dest,
opt_call_object_path,
interface_name,
method_name,
parameters,
NULL,
flags,
opt_call_timeout > 0 ? opt_call_timeout * 1000 : opt_call_timeout,
NULL,
&error);
#endif
if (result == NULL)
{
g_printerr (_("Error: %s\n"), error->message);
if (g_error_matches (error, G_DBUS_ERROR, G_DBUS_ERROR_INVALID_ARGS) && in_signature_types != NULL)
{
if (in_signature_types->len > 0)
{
GString *str;
str = g_string_new (NULL);
for (n = 0; n < in_signature_types->len; n++)
{
GVariantType *type = in_signature_types->pdata[n];
g_string_append_len (str,
g_variant_type_peek_string (type),
g_variant_type_get_string_length (type));
}
g_printerr ("(According to introspection data, you need to pass '%s')\n", str->str);
g_string_free (str, TRUE);
}
else
g_printerr ("(According to introspection data, you need to pass no arguments)\n");
}
g_error_free (error);
goto out;
}
s = g_variant_print (result, TRUE);
g_print ("%s\n", s);
g_free (s);
ret = TRUE;
out:
if (in_signature_types != NULL)
g_ptr_array_unref (in_signature_types);
if (result != NULL)
g_variant_unref (result);
if (c != NULL)
g_object_unref (c);
if (parameters != NULL)
g_variant_unref (parameters);
g_free (interface_name);
g_free (method_name);
g_option_context_free (o);
#ifdef G_OS_UNIX
g_clear_object (&fd_list);
#endif
return ret;
}
/* ---------------------------------------------------------------------------------------------------- */
static gchar *opt_introspect_dest = NULL;
static gchar *opt_introspect_object_path = NULL;
static gboolean opt_introspect_xml = FALSE;
static gboolean opt_introspect_recurse = FALSE;
static gboolean opt_introspect_only_properties = FALSE;
/* Introspect colors */
#define RESET_COLOR (use_colors? "\033[0m": "")
#define INTROSPECT_TITLE_COLOR (use_colors? UNDERLINE: "")
#define INTROSPECT_NODE_COLOR (use_colors? RESET_COLOR: "")
#define INTROSPECT_INTERFACE_COLOR (use_colors? YELLOW: "")
#define INTROSPECT_METHOD_COLOR (use_colors? BLUE: "")
#define INTROSPECT_SIGNAL_COLOR (use_colors? BLUE: "")
#define INTROSPECT_PROPERTY_COLOR (use_colors? MAGENTA: "")
#define INTROSPECT_INOUT_COLOR (use_colors? RESET_COLOR: "")
#define INTROSPECT_TYPE_COLOR (use_colors? GREEN: "")
#define INTROSPECT_ANNOTATION_COLOR (use_colors? RESET_COLOR: "")
static void
dump_annotation (const GDBusAnnotationInfo *o,
guint indent,
gboolean ignore_indent,
gboolean use_colors)
{
guint n;
g_print ("%*s%s@%s(\"%s\")%s\n",
ignore_indent ? 0 : indent, "",
INTROSPECT_ANNOTATION_COLOR, o->key, o->value, RESET_COLOR);
for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++)
dump_annotation (o->annotations[n], indent + 2, FALSE, use_colors);
}
static void
dump_arg (const GDBusArgInfo *o,
guint indent,
const gchar *direction,
gboolean ignore_indent,
gboolean include_newline,
gboolean use_colors)
{
guint n;
for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++)
{
dump_annotation (o->annotations[n], indent, ignore_indent, use_colors);
ignore_indent = FALSE;
}
g_print ("%*s%s%s%s%s%s%s %s%s",
ignore_indent ? 0 : indent, "",
INTROSPECT_INOUT_COLOR, direction, RESET_COLOR,
INTROSPECT_TYPE_COLOR, o->signature, RESET_COLOR,
o->name,
include_newline ? ",\n" : "");
}
static guint
count_args (GDBusArgInfo **args)
{
guint n;
n = 0;
if (args == NULL)
goto out;
while (args[n] != NULL)
n++;
out:
return n;
}
static void
dump_method (const GDBusMethodInfo *o,
guint indent,
gboolean use_colors)
{
guint n;
guint m;
guint name_len;
guint total_num_args;
for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++)
dump_annotation (o->annotations[n], indent, FALSE, use_colors);
g_print ("%*s%s%s%s(",
indent, "",
INTROSPECT_METHOD_COLOR, o->name, RESET_COLOR);
name_len = strlen (o->name);
total_num_args = count_args (o->in_args) + count_args (o->out_args);
for (n = 0, m = 0; o->in_args != NULL && o->in_args[n] != NULL; n++, m++)
{
gboolean ignore_indent = (m == 0);
gboolean include_newline = (m != total_num_args - 1);
dump_arg (o->in_args[n],
indent + name_len + 1,
"in ",
ignore_indent,
include_newline,
use_colors);
}
for (n = 0; o->out_args != NULL && o->out_args[n] != NULL; n++, m++)
{
gboolean ignore_indent = (m == 0);
gboolean include_newline = (m != total_num_args - 1);
dump_arg (o->out_args[n],
indent + name_len + 1,
"out ",
ignore_indent,
include_newline,
use_colors);
}
g_print (");\n");
}
static void
dump_signal (const GDBusSignalInfo *o,
guint indent,
gboolean use_colors)
{
guint n;
guint name_len;
guint total_num_args;
for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++)
dump_annotation (o->annotations[n], indent, FALSE, use_colors);
g_print ("%*s%s%s%s(",
indent, "",
INTROSPECT_SIGNAL_COLOR, o->name, RESET_COLOR);
name_len = strlen (o->name);
total_num_args = count_args (o->args);
for (n = 0; o->args != NULL && o->args[n] != NULL; n++)
{
gboolean ignore_indent = (n == 0);
gboolean include_newline = (n != total_num_args - 1);
dump_arg (o->args[n],
indent + name_len + 1,
"",
ignore_indent,
include_newline,
use_colors);
}
g_print (");\n");
}
static void
dump_property (const GDBusPropertyInfo *o,
guint indent,
gboolean use_colors,
GVariant *value)
{
const gchar *access;
guint n;
if (o->flags == G_DBUS_PROPERTY_INFO_FLAGS_READABLE)
access = "readonly";
else if (o->flags == G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE)
access = "writeonly";
else if (o->flags == (G_DBUS_PROPERTY_INFO_FLAGS_READABLE | G_DBUS_PROPERTY_INFO_FLAGS_WRITABLE))
access = "readwrite";
else
g_assert_not_reached ();
for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++)
dump_annotation (o->annotations[n], indent, FALSE, use_colors);
if (value != NULL)
{
gchar *s = g_variant_print (value, FALSE);
g_print ("%*s%s %s%s%s %s%s%s = %s;\n", indent, "", access,
INTROSPECT_TYPE_COLOR, o->signature, RESET_COLOR,
INTROSPECT_PROPERTY_COLOR, o->name, RESET_COLOR,
s);
g_free (s);
}
else
{
g_print ("%*s%s %s %s;\n", indent, "", access, o->signature, o->name);
}
}
static void
dump_interface (GDBusConnection *c,
const gchar *name,
const GDBusInterfaceInfo *o,
guint indent,
gboolean use_colors,
const gchar *object_path)
{
guint n;
GHashTable *properties;
properties = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
(GDestroyNotify) g_variant_unref);
/* Try to get properties */
if (c != NULL && name != NULL && object_path != NULL && o->properties != NULL)
{
GVariant *result;
result = g_dbus_connection_call_sync (c,
name,
object_path,
"org.freedesktop.DBus.Properties",
"GetAll",
g_variant_new ("(s)", o->name),
NULL,
G_DBUS_CALL_FLAGS_NONE,
3000,
NULL,
NULL);
if (result != NULL)
{
if (g_variant_is_of_type (result, G_VARIANT_TYPE ("(a{sv})")))
{
GVariantIter *iter;
GVariant *item;
g_variant_get (result,
"(a{sv})",
&iter);
while ((item = g_variant_iter_next_value (iter)))
{
gchar *key;
GVariant *value;
g_variant_get (item,
"{sv}",
&key,
&value);
g_hash_table_insert (properties, key, g_variant_ref (value));
}
}
g_variant_unref (result);
}
else
{
for (n = 0; o->properties != NULL && o->properties[n] != NULL; n++)
{
result = g_dbus_connection_call_sync (c,
name,
object_path,
"org.freedesktop.DBus.Properties",
"Get",
g_variant_new ("(ss)", o->name, o->properties[n]->name),
G_VARIANT_TYPE ("(v)"),
G_DBUS_CALL_FLAGS_NONE,
3000,
NULL,
NULL);
if (result != NULL)
{
GVariant *property_value;
g_variant_get (result,
"(v)",
&property_value);
g_hash_table_insert (properties,
g_strdup (o->properties[n]->name),
g_variant_ref (property_value));
g_variant_unref (result);
}
}
}
}
for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++)
dump_annotation (o->annotations[n], indent, FALSE, use_colors);
g_print ("%*s%sinterface %s%s {\n",
indent, "",
INTROSPECT_INTERFACE_COLOR, o->name, RESET_COLOR);
if (o->methods != NULL && !opt_introspect_only_properties)
{
g_print ("%*s %smethods%s:\n",
indent, "",
INTROSPECT_TITLE_COLOR, RESET_COLOR);
for (n = 0; o->methods[n] != NULL; n++)
dump_method (o->methods[n], indent + 4, use_colors);
}
if (o->signals != NULL && !opt_introspect_only_properties)
{
g_print ("%*s %ssignals%s:\n",
indent, "",
INTROSPECT_TITLE_COLOR, RESET_COLOR);
for (n = 0; o->signals[n] != NULL; n++)
dump_signal (o->signals[n], indent + 4, use_colors);
}
if (o->properties != NULL)
{
g_print ("%*s %sproperties%s:\n",
indent, "",
INTROSPECT_TITLE_COLOR, RESET_COLOR);
for (n = 0; o->properties[n] != NULL; n++)
{
dump_property (o->properties[n],
indent + 4,
use_colors,
g_hash_table_lookup (properties, (o->properties[n])->name));
}
}
g_print ("%*s};\n",
indent, "");
g_hash_table_unref (properties);
}
static gboolean
introspect_do (GDBusConnection *c,
const gchar *object_path,
guint indent,
gboolean use_colors);
static void
dump_node (GDBusConnection *c,
const gchar *name,
const GDBusNodeInfo *o,
guint indent,
gboolean use_colors,
const gchar *object_path,
gboolean recurse)
{
guint n;
const gchar *object_path_to_print;
object_path_to_print = object_path;
if (o->path != NULL)
object_path_to_print = o->path;
for (n = 0; o->annotations != NULL && o->annotations[n] != NULL; n++)
dump_annotation (o->annotations[n], indent, FALSE, use_colors);
g_print ("%*s%snode %s%s",
indent, "",
INTROSPECT_NODE_COLOR,
object_path_to_print != NULL ? object_path_to_print : "(not set)",
RESET_COLOR);
if (o->interfaces != NULL || o->nodes != NULL)
{
g_print (" {\n");
for (n = 0; o->interfaces != NULL && o->interfaces[n] != NULL; n++)
{
if (opt_introspect_only_properties)
{
if (o->interfaces[n]->properties != NULL && o->interfaces[n]->properties[0] != NULL)
dump_interface (c, name, o->interfaces[n], indent + 2, use_colors, object_path);
}
else
{
dump_interface (c, name, o->interfaces[n], indent + 2, use_colors, object_path);
}
}
for (n = 0; o->nodes != NULL && o->nodes[n] != NULL; n++)
{
if (recurse)
{
gchar *child_path;
if (g_variant_is_object_path (o->nodes[n]->path))
{
child_path = g_strdup (o->nodes[n]->path);
/* avoid infinite loops */
if (g_str_has_prefix (child_path, object_path))
{
introspect_do (c, child_path, indent + 2, use_colors);
}
else
{
g_print ("Skipping path %s that is not enclosed by parent %s\n",
child_path, object_path);
}
}
else
{
if (g_strcmp0 (object_path, "/") == 0)
child_path = g_strdup_printf ("/%s", o->nodes[n]->path);
else
child_path = g_strdup_printf ("%s/%s", object_path, o->nodes[n]->path);
introspect_do (c, child_path, indent + 2, use_colors);
}
g_free (child_path);
}
else
{
dump_node (NULL, NULL, o->nodes[n], indent + 2, use_colors, NULL, recurse);
}
}
g_print ("%*s};\n",
indent, "");
}
else
{
g_print ("\n");
}
}
static const GOptionEntry introspect_entries[] =
{
{ "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_introspect_dest, N_("Destination name to introspect"), NULL},
{ "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_introspect_object_path, N_("Object path to introspect"), NULL},
{ "xml", 'x', 0, G_OPTION_ARG_NONE, &opt_introspect_xml, N_("Print XML"), NULL},
{ "recurse", 'r', 0, G_OPTION_ARG_NONE, &opt_introspect_recurse, N_("Introspect children"), NULL},
{ "only-properties", 'p', 0, G_OPTION_ARG_NONE, &opt_introspect_only_properties, N_("Only print properties"), NULL},
G_OPTION_ENTRY_NULL
};
static gboolean
introspect_do (GDBusConnection *c,
const gchar *object_path,
guint indent,
gboolean use_colors)
{
GError *error;
GVariant *result;
GDBusNodeInfo *node;
gboolean ret;
const gchar *xml_data;
ret = FALSE;
node = NULL;
result = NULL;
error = NULL;
result = g_dbus_connection_call_sync (c,
opt_introspect_dest,
object_path,
"org.freedesktop.DBus.Introspectable",
"Introspect",
NULL,
G_VARIANT_TYPE ("(s)"),
G_DBUS_CALL_FLAGS_NONE,
3000, /* 3 sec */
NULL,
&error);
if (result == NULL)
{
g_printerr (_("Error: %s\n"), error->message);
g_error_free (error);
goto out;
}
g_variant_get (result, "(&s)", &xml_data);
if (opt_introspect_xml)
{
g_print ("%s", xml_data);
}
else
{
error = NULL;
node = g_dbus_node_info_new_for_xml (xml_data, &error);
if (node == NULL)
{
g_printerr (_("Error parsing introspection XML: %s\n"), error->message);
g_error_free (error);
goto out;
}
dump_node (c, opt_introspect_dest, node, indent, use_colors, object_path, opt_introspect_recurse);
}
ret = TRUE;
out:
if (node != NULL)
g_dbus_node_info_unref (node);
if (result != NULL)
g_variant_unref (result);
return ret;
}
static gboolean
handle_introspect (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;
gboolean complete_names;
gboolean complete_paths;
gboolean color_support;
ret = FALSE;
c = NULL;
modify_argv0_for_command (argc, argv, "introspect");
o = command_option_context_new (NULL, _("Introspect a remote object."),
introspect_entries, request_completion);
g_option_context_add_group (o, connection_get_group ());
complete_names = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0)
{
complete_names = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
complete_paths = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0)
{
complete_paths = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
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 (TRUE, &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;
}
if (complete_names)
{
print_names (c, FALSE);
goto out;
}
/* this only makes sense on message bus connections */
if (opt_introspect_dest == NULL)
{
if (request_completion)
g_print ("--dest \n");
else
g_printerr (_("Error: Destination is not specified\n"));
goto out;
}
if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0)
{
print_names (c, g_str_has_prefix (opt_introspect_dest, ":"));
goto out;
}
if (complete_paths)
{
print_paths (c, opt_introspect_dest, "/");
goto out;
}
if (!request_completion && !g_dbus_is_name (opt_introspect_dest))
{
g_printerr (_("Error: %s is not a valid bus name\n"), opt_introspect_dest);
goto out;
}
if (opt_introspect_object_path == NULL)
{
if (request_completion)
g_print ("--object-path \n");
else
g_printerr (_("Error: Object path is not specified\n"));
goto out;
}
if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0)
{
gchar *p;
s = g_strdup (opt_introspect_object_path);
p = strrchr (s, '/');
if (p != NULL)
{
if (p == s)
p++;
*p = '\0';
}
print_paths (c, opt_introspect_dest, s);
g_free (s);
goto out;
}
if (!request_completion && !g_variant_is_object_path (opt_introspect_object_path))
{
g_printerr (_("Error: %s is not a valid object path\n"), opt_introspect_object_path);
goto out;
}
if (request_completion && opt_introspect_object_path != NULL && !opt_introspect_recurse)
{
g_print ("--recurse \n");
}
if (request_completion && opt_introspect_object_path != NULL && !opt_introspect_only_properties)
{
g_print ("--only-properties \n");
}
/* All done with completion now */
if (request_completion)
goto out;
/* Before we start printing the actual info, check if we can do colors*/
color_support = g_log_writer_supports_color (fileno (stdout));
if (!introspect_do (c, opt_introspect_object_path, 0, color_support))
goto out;
ret = TRUE;
out:
if (c != NULL)
g_object_unref (c);
g_option_context_free (o);
return ret;
}
/* ---------------------------------------------------------------------------------------------------- */
static gchar *opt_monitor_dest = NULL;
static gchar *opt_monitor_object_path = NULL;
static guint monitor_filter_id = 0;
static void
monitor_signal_cb (GDBusConnection *connection,
const gchar *sender_name,
const gchar *object_path,
const gchar *interface_name,
const gchar *signal_name,
GVariant *parameters,
gpointer user_data)
{
gchar *s;
s = g_variant_print (parameters, TRUE);
g_print ("%s: %s.%s %s\n",
object_path,
interface_name,
signal_name,
s);
g_free (s);
}
static void
monitor_on_name_appeared (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
g_print ("The name %s is owned by %s\n", name, name_owner);
g_assert (monitor_filter_id == 0);
monitor_filter_id = g_dbus_connection_signal_subscribe (connection,
name_owner,
NULL, /* any interface */
NULL, /* any member */
opt_monitor_object_path,
NULL, /* arg0 */
G_DBUS_SIGNAL_FLAGS_NONE,
monitor_signal_cb,
NULL, /* user_data */
NULL); /* user_data destroy notify */
}
static void
monitor_on_name_vanished (GDBusConnection *connection,
const gchar *name,
gpointer user_data)
{
g_print ("The name %s does not have an owner\n", name);
if (monitor_filter_id != 0)
{
g_dbus_connection_signal_unsubscribe (connection, monitor_filter_id);
monitor_filter_id = 0;
}
}
static const GOptionEntry monitor_entries[] =
{
{ "dest", 'd', 0, G_OPTION_ARG_STRING, &opt_monitor_dest, N_("Destination name to monitor"), NULL},
{ "object-path", 'o', 0, G_OPTION_ARG_STRING, &opt_monitor_object_path, N_("Object path to monitor"), NULL},
G_OPTION_ENTRY_NULL
};
static gboolean
handle_monitor (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;
gboolean complete_names;
gboolean complete_paths;
GMainLoop *loop;
ret = FALSE;
c = NULL;
modify_argv0_for_command (argc, argv, "monitor");
o = command_option_context_new (NULL, _("Monitor a remote object."),
monitor_entries, request_completion);
g_option_context_add_group (o, connection_get_group ());
complete_names = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--dest") == 0)
{
complete_names = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
complete_paths = FALSE;
if (request_completion && *argc > 1 && g_strcmp0 ((*argv)[(*argc)-1], "--object-path") == 0)
{
complete_paths = TRUE;
remove_arg ((*argc) - 1, argc, argv);
}
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 (TRUE, &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;
}
/* Monitoring doesn’t make sense on a non-message-bus connection. */
if (g_dbus_connection_get_unique_name (c) == NULL)
{
if (!request_completion)
g_printerr (_("Error: can’t monitor a non-message-bus connection\n"));
goto out;
}
if (complete_names)
{
print_names (c, FALSE);
goto out;
}
/* this only makes sense on message bus connections */
if (opt_monitor_dest == NULL)
{
if (request_completion)
g_print ("--dest \n");
else
g_printerr (_("Error: Destination is not specified\n"));
goto out;
}
if (request_completion && g_strcmp0 ("--dest", completion_prev) == 0)
{
print_names (c, g_str_has_prefix (opt_monitor_dest, ":"));
goto out;
}
if (!request_completion && !g_dbus_is_name (opt_monitor_dest))
{
g_printerr (_("Error: %s is not a valid bus name\n"), opt_monitor_dest);
goto out;
}
if (complete_paths)
{
print_paths (c, opt_monitor_dest, "/");
goto out;
}
if (opt_monitor_object_path == NULL)
{
if (request_completion)
{
g_print ("--object-path \n");
goto out;
}
/* it's fine to not have an object path */
}
if (request_completion && g_strcmp0 ("--object-path", completion_prev) == 0)
{
gchar *p;
s = g_strdup (opt_monitor_object_path);
p = strrchr (s, '/');
if (p != NULL)
{
if (p == s)
p++;
*p = '\0';
}
print_paths (c, opt_monitor_dest, s);
g_free (s);
goto out;
}
if (!request_completion && (opt_monitor_object_path != NULL && !g_variant_is_object_path (opt_monitor_object_path)))
{
g_printerr (_("Error: %s is not a valid object path\n"), opt_monitor_object_path);
goto out;
}
/* All done with completion now */
if (request_completion)
goto out;
if (opt_monitor_object_path != NULL)
g_print ("Monitoring signals on object %s owned by %s\n", opt_monitor_object_path, opt_monitor_dest);
else
g_print ("Monitoring signals from all objects owned by %s\n", opt_monitor_dest);
loop = g_main_loop_new (NULL, FALSE);
g_bus_watch_name_on_connection (c,
opt_monitor_dest,
G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
monitor_on_name_appeared,
monitor_on_name_vanished,
NULL,
NULL);
g_main_loop_run (loop);
g_main_loop_unref (loop);
ret = TRUE;
out:
if (c != NULL)
g_object_unref (c);
g_option_context_free (o);
return ret;
}
/* ---------------------------------------------------------------------------------------------------- */
static gboolean opt_wait_activate_set = FALSE;
static gchar *opt_wait_activate_name = NULL;
static gint64 opt_wait_timeout_secs = 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_secs,
N_("Timeout to wait for before exiting with an error (seconds); 0 for "
"no timeout (default)"), "SECS" },
G_OPTION_ENTRY_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 = command_option_context_new (_("[OPTION…] BUS-NAME"),
_("Wait for a bus name to appear."),
wait_entries, request_completion);
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 (TRUE, &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;
}
/* 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_secs > 0)
timer_id = g_timeout_add_seconds (opt_wait_timeout_secs, 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,
gint *out_word_begins_at)
{
gint begin;
gint end;
if (s[0] == '\0')
{
if (out_word_begins_at != NULL)
*out_word_begins_at = -1;
return NULL;
}
if (g_ascii_isspace (s[cursor]) && ((cursor > 0 && g_ascii_isspace(s[cursor-1])) || cursor == 0))
{
if (out_word_begins_at != NULL)
*out_word_begins_at = cursor;
return g_strdup ("");
}
while (!g_ascii_isspace (s[cursor - 1]) && cursor > 0)
cursor--;
begin = cursor;
end = begin;
while (!g_ascii_isspace (s[end]) && s[end] != '\0')
end++;
if (out_word_begins_at != NULL)
*out_word_begins_at = begin;
return g_strndup (s + begin, end - begin);
}
gint
main (gint argc, gchar *argv[])
{
gint ret;
const gchar *command;
gboolean request_completion;
gchar *completion_cur;
gchar *completion_prev;
#ifdef G_OS_WIN32
gchar *tmp;
#endif
setlocale (LC_ALL, "");
textdomain (GETTEXT_PACKAGE);
#ifdef G_OS_WIN32
tmp = _glib_get_locale_dir ();
bindtextdomain (GETTEXT_PACKAGE, tmp);
g_free (tmp);
#else
bindtextdomain (GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
#endif
#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
#endif
ret = 1;
completion_cur = NULL;
completion_prev = NULL;
if (argc < 2)
{
usage (&argc, &argv, FALSE);
goto out;
}
request_completion = FALSE;
//completion_debug ("---- argc=%d --------------------------------------------------------", argc);
again:
command = argv[1];
if (g_strcmp0 (command, "help") == 0)
{
if (request_completion)
{
/* do nothing */
}
else
{
usage (&argc, &argv, TRUE);
ret = 0;
}
goto out;
}
else if (g_strcmp0 (command, "emit") == 0)
{
if (handle_emit (&argc,
&argv,
request_completion,
completion_cur,
completion_prev))
ret = 0;
goto out;
}
else if (g_strcmp0 (command, "call") == 0)
{
if (handle_call (&argc,
&argv,
request_completion,
completion_cur,
completion_prev))
ret = 0;
goto out;
}
else if (g_strcmp0 (command, "introspect") == 0)
{
if (handle_introspect (&argc,
&argv,
request_completion,
completion_cur,
completion_prev))
ret = 0;
goto out;
}
else if (g_strcmp0 (command, "monitor") == 0)
{
if (handle_monitor (&argc,
&argv,
request_completion,
completion_cur,
completion_prev))
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;
}
#ifdef G_OS_WIN32
else if (g_strcmp0 (command, _GDBUS_ARG_WIN32_RUN_SESSION_BUS) == 0)
{
g_win32_run_session_bus (NULL, NULL, NULL, 0);
ret = 0;
goto out;
}
#endif
else if (g_strcmp0 (command, "complete") == 0 && argc == 4 && !request_completion)
{
const gchar *completion_line;
gchar **completion_argv;
gint completion_argc;
gint completion_point;
gchar *endp;
gint cur_begin;
request_completion = TRUE;
completion_line = argv[2];
completion_point = strtol (argv[3], &endp, 10);
if (endp == argv[3] || *endp != '\0')
goto out;
#if 0
completion_debug ("completion_point=%d", completion_point);
completion_debug ("----");
completion_debug (" 0123456789012345678901234567890123456789012345678901234567890123456789");
completion_debug ("'%s'", completion_line);
completion_debug (" %*s^",
completion_point, "");
completion_debug ("----");
#endif
if (!g_shell_parse_argv (completion_line,
&completion_argc,
&completion_argv,
NULL))
{
/* it's very possible the command line can't be parsed (for
* example, missing quotes etc) - in that case, we just
* don't autocomplete at all
*/
goto out;
}
/* compute cur and prev */
completion_prev = NULL;
completion_cur = pick_word_at (completion_line, completion_point, &cur_begin);
if (cur_begin > 0)
{
gint prev_end;
for (prev_end = cur_begin - 1; prev_end >= 0; prev_end--)
{
if (!g_ascii_isspace (completion_line[prev_end]))
{
completion_prev = pick_word_at (completion_line, prev_end, NULL);
break;
}
}
}
#if 0
completion_debug (" cur='%s'", completion_cur);
completion_debug ("prev='%s'", completion_prev);
#endif
argc = completion_argc;
argv = completion_argv;
ret = 0;
goto again;
}
else
{
if (request_completion)
{
g_print ("help \nemit \ncall \nintrospect \nmonitor \nwait \n");
ret = 0;
goto out;
}
else
{
g_printerr ("Unknown command '%s'\n", command);
usage (&argc, &argv, FALSE);
goto out;
}
}
out:
g_free (completion_cur);
g_free (completion_prev);
return ret;
}