From 91f6977a2dc01207072b5718b89c57f439f9339a Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Thu, 31 Jul 2008 12:28:07 -0400 Subject: [PATCH] add bash completion for dbus-send(1) For now, it's in dbus-glib since dbus doesn't have an introspection XML parser (yet). --- configure.ac | 13 + dbus/Makefile.am | 17 ++- dbus/dbus-bash-completion-helper.c | 513 ++++++++++++++++++++++++++++++++++++ dbus/dbus-bash-completion.sh.in | 21 ++ 4 files changed, 563 insertions(+), 1 deletions(-) create mode 100644 dbus/dbus-bash-completion-helper.c create mode 100644 dbus/dbus-bash-completion.sh.in diff --git a/configure.ac b/configure.ac index 839e0e1..a163935 100644 --- a/configure.ac +++ b/configure.ac @@ -54,11 +54,23 @@ AC_ARG_ENABLE(asserts, AS_HELP_STRING([--enable-asserts],[include assertion chec AC_ARG_ENABLE(checks, AS_HELP_STRING([--enable-checks],[include sanity checks on public API]),enable_checks=$enableval,enable_checks=yes) AC_ARG_ENABLE(doxygen-docs, AS_HELP_STRING([--enable-doxygen-docs],[build DOXYGEN documentation (requires Doxygen)]),enable_doxygen_docs=$enableval,enable_doxygen_docs=auto) AC_ARG_ENABLE(gcov, AS_HELP_STRING([--enable-gcov],[compile with coverage profiling instrumentation (gcc only)]),enable_gcov=$enableval,enable_gcov=no) +AC_ARG_ENABLE(bash-completion, AS_HELP_STRING([--enable-bash-completion],[install bash completion scripts]),enable_bash_completion=$enableval,enable_bash_completion=yes) AC_ARG_WITH(test-socket-dir, AS_HELP_STRING([--with-test-socket-dir=[dirname]],[Where to put sockets for make check])) AC_ARG_WITH(introspect-xml, AS_HELP_STRING([--with-introspect-xml=[filename]],[Pass in a pregenerated dbus daemon introspection xml file (as generated by 'dbus-daemon --introspect') to use instead of querying the installed dbus daemon])) + +AM_CONDITIONAL(DBUS_BASH_COMPLETION, test x$enable_bash_completion = xyes) +if test x$enable_bash_completion = xyes; then + AC_DEFINE(DBUS_BASH_COMPLETION,1,[Enable bash completion]) +fi + +if test x$enable_verbose_mode = xyes; then + AC_DEFINE(DBUS_ENABLE_VERBOSE_MODE,1,[Support a verbose mode]) +fi + + dnl DBUS_BUILD_TESTS controls unit tests built in to .c files dnl and also some stuff in the test/ subdir AM_CONDITIONAL(DBUS_BUILD_TESTS, test x$enable_tests = xyes) @@ -867,6 +879,7 @@ echo " Building checks: ${enable_checks} Building Doxygen docs: ${enable_doxygen_docs} Building Gtk-doc docs: ${enable_gtk_doc} + Bash Completion: ${enable_bash_completion} Gettext libs (empty OK): ${INTLLIBS} Using XML parser: ${with_xml} 'make check' socket dir: ${TEST_SOCKET_DIR} diff --git a/dbus/Makefile.am b/dbus/Makefile.am index 2650e8b..bd01d7f 100644 --- a/dbus/Makefile.am +++ b/dbus/Makefile.am @@ -91,7 +91,22 @@ regenerate-built-sources: echo '#include "dbus-gmarshal.h"' > dbus-gmarshal.c && \ @GLIB_GENMARSHAL@ --prefix=_dbus_g_marshal dbus-gmarshal.list --body >> dbus-gmarshal.c -EXTRA_DIST=dbus-gmarshal.list make-dbus-glib-error-switch.sh make-dbus-glib-error-enum.sh + +profiledir = $(sysconfdir)/profile.d +if DBUS_BASH_COMPLETION +libexec_PROGRAMS=dbus-bash-completion-helper +profile_SCRIPTS=dbus-bash-completion.sh +endif + +dbus-bash-completion.sh : dbus-bash-completion.sh.in + @sed -e "s|\@libexecdir\@|$(libexecdir)|" $< > $@ + +dbus_bash_completion_helper_SOURCES = \ + dbus-bash-completion-helper.c +dbus_bash_completion_helper_LDADD=$(DBUS_LIBS) $(DBUS_GLIB_LIBS) -lexpat libdbus-gtool.la libdbus-glib-1.la + + +EXTRA_DIST=dbus-gmarshal.list make-dbus-glib-error-switch.sh make-dbus-glib-error-enum.sh dbus-bash-completion.sh.in if DBUS_BUILD_TESTS diff --git a/dbus/dbus-bash-completion-helper.c b/dbus/dbus-bash-completion-helper.c new file mode 100644 index 0000000..022c722 --- /dev/null +++ b/dbus/dbus-bash-completion-helper.c @@ -0,0 +1,513 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ +/* dbus-bash-completion-helper.c Bash Completion helper routines + * + * Copyright (C) 2008 David Zeuthen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include "dbus-gparser.h" + +static void +print_services (DBusConnection *connection) +{ + DBusMessage *message; + DBusMessage *reply; + DBusError error; + DBusMessageIter iter; + DBusMessageIter iter_array; + const char *name; + + /* list both active and activatable names (the shell will sort and + * uniquify them) - also avoid names that are not well-known + * (e.g. :1.42). + */ + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ListNames"); + dbus_error_init (&error); + reply = dbus_connection_send_with_reply_and_block (connection, + message, + -1, + &error); + dbus_message_unref (message); + dbus_message_iter_init (reply, &iter); + dbus_message_iter_recurse (&iter, &iter_array); + while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID) + { + dbus_message_iter_get_basic (&iter_array, &name); + if (name[0] != ':') + printf ("%s \n", name); + dbus_message_iter_next (&iter_array); + } + dbus_message_unref (reply); + + message = dbus_message_new_method_call (DBUS_SERVICE_DBUS, + DBUS_PATH_DBUS, + DBUS_INTERFACE_DBUS, + "ListActivatableNames"); + dbus_error_init (&error); + reply = dbus_connection_send_with_reply_and_block (connection, + message, + -1, + &error); + dbus_message_unref (message); + dbus_message_iter_init (reply, &iter); + dbus_message_iter_recurse (&iter, &iter_array); + while (dbus_message_iter_get_arg_type (&iter_array) != DBUS_TYPE_INVALID) + { + dbus_message_iter_get_basic (&iter_array, &name); + printf ("%s \n", name); + dbus_message_iter_next (&iter_array); + } + dbus_message_unref (reply); +} + +static gboolean +have_option (char **tokens, const char *option) +{ + int n; + for (n = 0; tokens[n] != NULL; n++) + if (strcmp (tokens[n], option) == 0) + return TRUE; + return FALSE; +} + +static gboolean +have_option_with_value (char **tokens, const char *option, const char **value) +{ + int n; + for (n = 0; tokens[n] != NULL; n++) + { + if (g_str_has_prefix (tokens[n], option)) + { + if (strlen (tokens[n]) > strlen (option)) + *value = tokens[n] + strlen (option); + return TRUE; + } + } + return FALSE; +} + +static void +print_objects (DBusConnection *connection, const char *service_name, const char *cur) +{ + DBusMessage *message; + DBusMessage *reply; + DBusError error; + DBusMessageIter iter; + const char *introspection_xml; + NodeInfo *root; + GSList *nodes; + GSList *l; + + if (cur == NULL) + cur = "/"; + + message = dbus_message_new_method_call (service_name, + cur, + DBUS_INTERFACE_INTROSPECTABLE, + "Introspect"); + dbus_error_init (&error); + reply = dbus_connection_send_with_reply_and_block (connection, + message, + -1, + &error); + dbus_message_unref (message); + dbus_message_iter_init (reply, &iter); + dbus_message_iter_get_basic (&iter, &introspection_xml); + + root = description_load_from_string (introspection_xml, strlen (introspection_xml), NULL); + nodes = node_info_get_nodes (root); + + if (g_slist_length (node_info_get_interfaces (root)) > 0) + printf ("%s \n", cur); + + for (l = nodes; l != NULL; l = l->next) + { + NodeInfo *node = (NodeInfo *) l->data; + const char *name; + char *new_path; + + name = node_info_get_name (node); + if (strcmp (cur, "/") == 0) + new_path = g_strdup_printf ("/%s", name); + else + new_path = g_strdup_printf ("%s/%s", cur, name); + + print_objects (connection, service_name, new_path); + + g_free (new_path); + } + node_info_unref (root); + + dbus_message_unref (reply); +} + +static gboolean +is_object_path_with_interfaces (DBusConnection *connection, const char *service_name, const char *object_path) +{ + DBusMessage *message; + DBusMessage *reply; + DBusError error; + DBusMessageIter iter; + const char *introspection_xml; + NodeInfo *root; + gboolean ret; + + ret = FALSE; + + message = dbus_message_new_method_call (service_name, + object_path, + DBUS_INTERFACE_INTROSPECTABLE, + "Introspect"); + dbus_error_init (&error); + reply = dbus_connection_send_with_reply_and_block (connection, + message, + -1, + &error); + dbus_message_unref (message); + dbus_message_iter_init (reply, &iter); + dbus_message_iter_get_basic (&iter, &introspection_xml); + + root = description_load_from_string (introspection_xml, strlen (introspection_xml), NULL); + + if (g_slist_length (node_info_get_interfaces (root)) > 0) + ret = TRUE; + + node_info_unref (root); + dbus_message_unref (reply); + + return ret; +} + +static void +print_methods (DBusConnection *connection, const char *service_name, const char *object_path) +{ + DBusMessage *message; + DBusMessage *reply; + DBusError error; + DBusMessageIter iter; + const char *introspection_xml; + NodeInfo *root; + GSList *interfaces; + GSList *l; + + message = dbus_message_new_method_call (service_name, + object_path, + DBUS_INTERFACE_INTROSPECTABLE, + "Introspect"); + dbus_error_init (&error); + reply = dbus_connection_send_with_reply_and_block (connection, + message, + -1, + &error); + dbus_message_unref (message); + dbus_message_iter_init (reply, &iter); + dbus_message_iter_get_basic (&iter, &introspection_xml); + + root = description_load_from_string (introspection_xml, strlen (introspection_xml), NULL); + interfaces = node_info_get_interfaces (root); + for (l = interfaces; l != NULL; l = l->next) + { + InterfaceInfo *interface = (InterfaceInfo *) l->data; + GSList *methods; + GSList *ll; + methods = interface_info_get_methods (interface); + for (ll = methods; ll != NULL; ll = ll->next) + { + MethodInfo *method = (MethodInfo *) ll->data; + printf ("%s.%s \n", interface_info_get_name (interface), method_info_get_name (method)); + } + } + node_info_unref (root); + dbus_message_unref (reply); +} + +static void +print_signature (DBusConnection *connection, const char *service_name, const char *object_path, const char *method) +{ + DBusMessage *message; + DBusMessage *reply; + DBusError error; + DBusMessageIter iter; + const char *introspection_xml; + NodeInfo *root; + GSList *interfaces; + GSList *l; + char *s; + char *method_name; + char *interface_name; + int n; + + method_name = NULL; + interface_name = NULL; + + s = strrchr (method, '.'); + if (s == NULL || strlen (s) < 2 || s - method < 1) + goto fail; + method_name = g_strdup (s + 1); + interface_name = g_strndup (method, s - method); + printf (" \n"); + + message = dbus_message_new_method_call (service_name, + object_path, + DBUS_INTERFACE_INTROSPECTABLE, + "Introspect"); + dbus_error_init (&error); + reply = dbus_connection_send_with_reply_and_block (connection, + message, + -1, + &error); + dbus_message_unref (message); + dbus_message_iter_init (reply, &iter); + dbus_message_iter_get_basic (&iter, &introspection_xml); + + root = description_load_from_string (introspection_xml, strlen (introspection_xml), NULL); + interfaces = node_info_get_interfaces (root); + for (l = interfaces; l != NULL; l = l->next) + { + InterfaceInfo *interface = (InterfaceInfo *) l->data; + + if (strcmp (interface_name, interface_info_get_name (interface)) == 0) + { + GSList *methods; + GSList *ll; + methods = interface_info_get_methods (interface); + for (ll = methods; ll != NULL; ll = ll->next) + { + MethodInfo *method = (MethodInfo *) ll->data; + if (strcmp (method_name, method_info_get_name (method)) == 0) + { + GSList *args; + GSList *lll; + args = method_info_get_args (method); + for (lll = args, n = 0; lll != NULL; lll = lll->next, n++) + { + ArgInfo *arg = (ArgInfo *) lll->data; + printf ("# %s: arg %d: %s (%s) \n", + arg_info_get_direction (arg) == ARG_IN ? " IN" : "OUT", + n, + arg_info_get_name (arg), + arg_info_get_type (arg)); + } + break; + } + } + } + } + node_info_unref (root); + dbus_message_unref (reply); + fail: + g_free (method_name); + g_free (interface_name); +} + + +static int +complete_dbus_send (char *str) +{ + int ret; + char **tokens; + int num_tokens; + const char *cur; + const char *prev; + gboolean have_system; + gboolean have_session; + gboolean have_print_reply; + gboolean have_dest; + DBusConnection *connection; + DBusBusType bus_type; + DBusError error; + const char *target_service; + const char *object_path; + const char *method; + int n; + int object_path_index; + + ret = 1; + connection = NULL; + target_service = NULL; + + tokens = g_strsplit (str, " ", 0); + num_tokens = g_strv_length (tokens); + if (num_tokens >= 2) { + cur = tokens[num_tokens - 1]; + prev = tokens[num_tokens - 2]; + } else if (num_tokens == 1) { + cur = tokens[num_tokens - 1]; + prev = ""; + } else { + cur = ""; + prev = ""; + } + + have_system = have_option (tokens, "--system"); + have_session = have_option (tokens, "--session"); + have_print_reply = have_option (tokens, "--print-reply"); + have_dest = have_option_with_value (tokens, "--dest=", &target_service); + + if (!have_print_reply) + printf ("--print-reply \n"); + + if (!have_system && !have_session) + { + printf ("--system \n"); + printf ("--session \n"); + goto done; + } + + if (!have_dest && !g_str_has_prefix (cur, "--dest=")) + { + printf ("--dest=\n"); + goto done; + } + + if (have_system || have_session) + { + bus_type = have_system ? DBUS_BUS_SYSTEM : DBUS_BUS_SESSION; + + dbus_error_init (&error); + connection = dbus_bus_get (bus_type, &error); + if (connection == NULL) + { + fprintf (stderr, "Failed to open connection to %s message bus: %s: %s\n", + (bus_type == DBUS_BUS_SYSTEM) ? "system" : "session", + error.name, error.message); + dbus_error_free (&error); + goto fail; + } + } + + if (connection != NULL && g_str_has_prefix (cur, "--dest=")) + { + print_services (connection); + goto done; + } + + /* see if we have an object path */ + object_path = NULL; + object_path_index = 0; + if (connection != NULL && target_service != NULL) + { + for (n = 0; tokens[n] != NULL; n++) + { + if (tokens[n] == cur) + continue; + + if (*(tokens[n]) == '/') + { + if (is_object_path_with_interfaces (connection, target_service, tokens[n])) + { + object_path = tokens[n]; + object_path_index = n; + } + } + } + } + + /* if we have a connection and a destination but no object path, go ahead and list the object paths */ + if (connection != NULL && target_service != NULL && object_path == NULL) + { + print_objects (connection, target_service, NULL); + goto done; + } + + /* see if we have a method; it's directly after the object_path */ + method = NULL; + if (connection != NULL && target_service != NULL && object_path != NULL) + { + if ((object_path_index + 1 < num_tokens - 1) && + (strlen (tokens[object_path_index + 1]) > 0) && + !(strcmp (cur, tokens[object_path_index + 1]) == 0)) + method = tokens[object_path_index + 1]; + } + + /* if we have connection, destination and object path but no method yet, list the methods */ + if (connection != NULL && target_service != NULL && object_path != NULL && method == NULL) + { + print_methods (connection, target_service, object_path); + goto done; + } + + /* print signature as comment */ + if (connection != NULL && target_service != NULL && object_path != NULL && method != NULL) + { + print_signature (connection, target_service, object_path, method); + } + + done: + ret = 0; + + fail: + + g_strfreev (tokens); + + if (connection != NULL) + dbus_connection_unref (connection); + return ret; +} + +int +main (int argc, char *argv[]) +{ + int ret; + char *cur; + gboolean dbus_send; + + ret = 1; + dbus_send = FALSE; + + if (argc != 3) + { + fprintf (stderr, "invalid use\n"); + goto out; + } + + if (strcmp (argv[1], "dbus-send") == 0) + { + dbus_send = TRUE; + } + else + { + fprintf (stderr, "unknown program '%s'\n", argv[1]); + goto out; + } + + if (strlen (argv[2]) < strlen (argv[1]) + 1) + { + fprintf (stderr, "error"); + goto out; + } + + cur = argv[2] + strlen (argv[1]) + 1; + + if (dbus_send) + ret = complete_dbus_send (cur); + + out: + return ret; +} diff --git a/dbus/dbus-bash-completion.sh.in b/dbus/dbus-bash-completion.sh.in new file mode 100644 index 0000000..a7751da --- /dev/null +++ b/dbus/dbus-bash-completion.sh.in @@ -0,0 +1,21 @@ + +# Check for bash +[ -z "$BASH_VERSION" ] && return + +################################################################################ + +__dbus_send() { + local IFS=$'\n' + local cur="${COMP_WORDS[COMP_CWORD]}" + + # --name=value style option + if [[ "$cur" == *=* ]] ; then + cur=${cur/*=/} + fi + + COMPREPLY=($(compgen -W "$(@libexecdir@/dbus-bash-completion-helper dbus-send ${COMP_WORDS[@]:0})" -- $cur)) +} + +################################################################################ + +complete -o nospace -F __dbus_send dbus-send -- 1.5.6.4