Emeric Vigier | 2f62582 | 2012-08-06 11:09:52 -0400 | [diff] [blame] | 1 | /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ |
| 2 | /* dbus-viewer.c Graphical D-Bus frontend utility |
| 3 | * |
| 4 | * Copyright (C) 2003 Red Hat, Inc. |
| 5 | * |
| 6 | * Licensed under the Academic Free License version 2.1 |
| 7 | * |
| 8 | * This program is free software; you can redistribute it and/or modify |
| 9 | * it under the terms of the GNU General Public License as published by |
| 10 | * the Free Software Foundation; either version 2 of the License, or |
| 11 | * (at your option) any later version. |
| 12 | * |
| 13 | * This program is distributed in the hope that it will be useful, |
| 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | * GNU General Public License for more details. |
| 17 | * |
| 18 | * You should have received a copy of the GNU General Public License |
| 19 | * along with this program; if not, write to the Free Software |
| 20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| 21 | * |
| 22 | */ |
| 23 | #include <config.h> |
| 24 | #include <stdlib.h> |
| 25 | #include <errno.h> |
| 26 | #include <stdio.h> |
| 27 | #include <string.h> |
| 28 | #include <gtk/gtk.h> |
| 29 | #include "dbus-tree-view.h" |
| 30 | #include "dbus-names-model.h" |
| 31 | #include <glib/dbus-gparser.h> |
| 32 | #include <glib/dbus-gutils.h> |
| 33 | #include <dbus/dbus-glib.h> |
| 34 | #include <glib/gi18n.h> |
| 35 | |
| 36 | static void |
| 37 | show_error_dialog (GtkWindow *transient_parent, |
| 38 | GtkWidget **weak_ptr, |
| 39 | const char *message_format, |
| 40 | ...) |
| 41 | { |
| 42 | char *message; |
| 43 | va_list args; |
| 44 | |
| 45 | if (message_format) |
| 46 | { |
| 47 | va_start (args, message_format); |
| 48 | message = g_strdup_vprintf (message_format, args); |
| 49 | va_end (args); |
| 50 | } |
| 51 | else |
| 52 | message = NULL; |
| 53 | |
| 54 | if (weak_ptr == NULL || *weak_ptr == NULL) |
| 55 | { |
| 56 | GtkWidget *dialog; |
| 57 | dialog = gtk_message_dialog_new (transient_parent, |
| 58 | GTK_DIALOG_DESTROY_WITH_PARENT, |
| 59 | GTK_MESSAGE_ERROR, |
| 60 | GTK_BUTTONS_CLOSE, |
| 61 | message); |
| 62 | |
| 63 | g_signal_connect (G_OBJECT (dialog), "response", G_CALLBACK (gtk_widget_destroy), NULL); |
| 64 | |
| 65 | if (weak_ptr != NULL) |
| 66 | { |
| 67 | *weak_ptr = dialog; |
| 68 | g_object_add_weak_pointer (G_OBJECT (dialog), (void**)weak_ptr); |
| 69 | } |
| 70 | |
| 71 | gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); |
| 72 | |
| 73 | gtk_widget_show_all (dialog); |
| 74 | } |
| 75 | else |
| 76 | { |
| 77 | g_return_if_fail (GTK_IS_MESSAGE_DIALOG (*weak_ptr)); |
| 78 | |
| 79 | gtk_label_set_text (GTK_LABEL (GTK_MESSAGE_DIALOG (*weak_ptr)->label), message); |
| 80 | |
| 81 | gtk_window_present (GTK_WINDOW (*weak_ptr)); |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | typedef struct |
| 86 | { |
| 87 | DBusGConnection *connection; |
| 88 | |
| 89 | GtkWidget *window; |
| 90 | GtkWidget *treeview; |
| 91 | GtkWidget *name_menu; |
| 92 | |
| 93 | GtkTreeModel *names_model; |
| 94 | |
| 95 | GtkWidget *error_dialog; |
| 96 | |
| 97 | } TreeWindow; |
| 98 | |
| 99 | |
| 100 | static void |
| 101 | tree_window_set_node (TreeWindow *w, |
| 102 | NodeInfo *node) |
| 103 | { |
| 104 | char **path; |
| 105 | const char *name; |
| 106 | |
| 107 | name = node_info_get_name (node); |
| 108 | if (name == NULL || |
| 109 | name[0] != '/') |
| 110 | { |
| 111 | g_printerr (_("Assuming root node is at path /, since no absolute path is specified")); |
| 112 | name = "/"; |
| 113 | } |
| 114 | |
| 115 | path = _dbus_gutils_split_path (name); |
| 116 | |
| 117 | dbus_tree_view_update (GTK_TREE_VIEW (w->treeview), |
| 118 | (const char**) path, |
| 119 | node); |
| 120 | |
| 121 | g_strfreev (path); |
| 122 | } |
| 123 | |
| 124 | typedef struct |
| 125 | { |
| 126 | DBusGConnection *connection; |
| 127 | char *service_name; |
| 128 | GError *error; |
| 129 | NodeInfo *node; |
| 130 | TreeWindow *window; /* Not touched from child thread */ |
| 131 | } LoadFromServiceData; |
| 132 | |
| 133 | static gboolean |
| 134 | load_child_nodes (const char *service_name, |
| 135 | NodeInfo *parent, |
| 136 | GString *path, |
| 137 | GError **error) |
| 138 | { |
| 139 | DBusGConnection *connection; |
| 140 | GSList *tmp; |
| 141 | |
| 142 | connection = dbus_g_bus_get (DBUS_BUS_SESSION, error); |
| 143 | if (connection == NULL) |
| 144 | return FALSE; |
| 145 | |
| 146 | tmp = node_info_get_nodes (parent); |
| 147 | while (tmp != NULL) |
| 148 | { |
| 149 | DBusGProxy *proxy; |
| 150 | char *data; |
| 151 | NodeInfo *child; |
| 152 | NodeInfo *complete_child; |
| 153 | int save_len; |
| 154 | |
| 155 | complete_child = NULL; |
| 156 | |
| 157 | child = tmp->data; |
| 158 | |
| 159 | save_len = path->len; |
| 160 | |
| 161 | if (save_len > 1) |
| 162 | g_string_append (path, "/"); |
| 163 | g_string_append (path, base_info_get_name ((BaseInfo*)child)); |
| 164 | |
| 165 | if (*service_name == ':') |
| 166 | { |
| 167 | proxy = dbus_g_proxy_new_for_name (connection, |
| 168 | service_name, |
| 169 | path->str, |
| 170 | DBUS_INTERFACE_INTROSPECTABLE); |
| 171 | g_assert (proxy != NULL); |
| 172 | } |
| 173 | else |
| 174 | { |
| 175 | proxy = dbus_g_proxy_new_for_name_owner (connection, |
| 176 | service_name, |
| 177 | path->str, |
| 178 | DBUS_INTERFACE_INTROSPECTABLE, |
| 179 | error); |
| 180 | if (proxy == NULL) |
| 181 | goto done; |
| 182 | } |
| 183 | |
| 184 | if (!dbus_g_proxy_call (proxy, "Introspect", error, |
| 185 | G_TYPE_INVALID, |
| 186 | G_TYPE_STRING, &data, |
| 187 | G_TYPE_INVALID)) |
| 188 | goto done; |
| 189 | |
| 190 | complete_child = description_load_from_string (data, -1, error); |
| 191 | g_free (data); |
| 192 | if (complete_child == NULL) |
| 193 | { |
| 194 | g_printerr ("%s\n", data); |
| 195 | goto done; |
| 196 | } |
| 197 | |
| 198 | done: |
| 199 | g_object_unref (proxy); |
| 200 | |
| 201 | if (complete_child == NULL) |
| 202 | return FALSE; |
| 203 | |
| 204 | /* change complete_child's name to relative */ |
| 205 | base_info_set_name ((BaseInfo*)complete_child, |
| 206 | base_info_get_name ((BaseInfo*)child)); |
| 207 | |
| 208 | /* Stitch in complete_child rather than child */ |
| 209 | node_info_replace_node (parent, child, complete_child); |
| 210 | node_info_unref (complete_child); /* ref still held by parent */ |
| 211 | |
| 212 | /* Now recurse */ |
| 213 | if (!load_child_nodes (service_name, complete_child, path, error)) |
| 214 | return FALSE; |
| 215 | |
| 216 | /* restore path */ |
| 217 | g_string_set_size (path, save_len); |
| 218 | |
| 219 | tmp = tmp->next; |
| 220 | } |
| 221 | |
| 222 | return TRUE; |
| 223 | } |
| 224 | |
| 225 | static gboolean |
| 226 | load_from_service_complete_idle (void *data) |
| 227 | { |
| 228 | /* Called in main thread */ |
| 229 | GThread *thread = data; |
| 230 | LoadFromServiceData *d; |
| 231 | NodeInfo *node; |
| 232 | |
| 233 | d = g_thread_join (thread); |
| 234 | |
| 235 | node = d->node; |
| 236 | |
| 237 | if (d->error) |
| 238 | { |
| 239 | g_assert (d->node == NULL); |
| 240 | show_error_dialog (GTK_WINDOW (d->window->window), &d->window->error_dialog, |
| 241 | _("Unable to load \"%s\": %s\n"), |
| 242 | d->service_name, d->error->message); |
| 243 | g_error_free (d->error); |
| 244 | } |
| 245 | else |
| 246 | { |
| 247 | g_assert (d->error == NULL); |
| 248 | |
| 249 | tree_window_set_node (d->window, node); |
| 250 | node_info_unref (node); |
| 251 | } |
| 252 | |
| 253 | g_free (d->service_name); |
| 254 | dbus_g_connection_unref (d->connection); |
| 255 | g_free (d); |
| 256 | |
| 257 | return FALSE; |
| 258 | } |
| 259 | |
| 260 | static void* |
| 261 | load_from_service_thread_func (void *thread_data) |
| 262 | { |
| 263 | DBusGProxy *root_proxy; |
| 264 | const char *data; |
| 265 | NodeInfo *node; |
| 266 | GString *path; |
| 267 | LoadFromServiceData *lfsd; |
| 268 | |
| 269 | lfsd = thread_data; |
| 270 | |
| 271 | node = NULL; |
| 272 | path = NULL; |
| 273 | |
| 274 | #if 1 |
| 275 | /* this will end up autolaunching the service when we introspect it */ |
| 276 | root_proxy = dbus_g_proxy_new_for_name (lfsd->connection, |
| 277 | lfsd->service_name, |
| 278 | "/", |
| 279 | DBUS_INTERFACE_INTROSPECTABLE); |
| 280 | g_assert (root_proxy != NULL); |
| 281 | #else |
| 282 | /* this will be an error if the service doesn't exist */ |
| 283 | root_proxy = dbus_g_proxy_new_for_name_owner (lfsd->connection, |
| 284 | lfsd->service_name, |
| 285 | "/", |
| 286 | DBUS_INTERFACE_INTROSPECTABLE, |
| 287 | &lfsd->error); |
| 288 | if (root_proxy == NULL) |
| 289 | { |
| 290 | g_printerr ("Failed to get owner of '%s'\n", lfsd->service_name); |
| 291 | return lfsd->data; |
| 292 | } |
| 293 | #endif |
| 294 | |
| 295 | if (!dbus_g_proxy_call (root_proxy, "Introspect", &lfsd->error, |
| 296 | G_TYPE_INVALID, |
| 297 | G_TYPE_STRING, &data, |
| 298 | G_TYPE_INVALID)) |
| 299 | { |
| 300 | g_printerr ("Failed to Introspect() %s\n", |
| 301 | dbus_g_proxy_get_bus_name (root_proxy)); |
| 302 | goto out; |
| 303 | } |
| 304 | |
| 305 | node = description_load_from_string (data, -1, &lfsd->error); |
| 306 | |
| 307 | /* g_print ("%s\n", data); */ |
| 308 | |
| 309 | if (node == NULL) |
| 310 | goto out; |
| 311 | |
| 312 | base_info_set_name ((BaseInfo*)node, "/"); |
| 313 | |
| 314 | path = g_string_new ("/"); |
| 315 | |
| 316 | if (!load_child_nodes (dbus_g_proxy_get_bus_name (root_proxy), |
| 317 | node, path, &lfsd->error)) |
| 318 | { |
| 319 | node_info_unref (node); |
| 320 | node = NULL; |
| 321 | goto out; |
| 322 | } |
| 323 | |
| 324 | out: |
| 325 | g_object_unref (root_proxy); |
| 326 | |
| 327 | if (path) |
| 328 | g_string_free (path, TRUE); |
| 329 | |
| 330 | lfsd->node = node; |
| 331 | g_assert (lfsd->node || lfsd->error); |
| 332 | g_assert (lfsd->node == NULL || lfsd->error == NULL); |
| 333 | |
| 334 | /* Add idle to main thread that will join us back */ |
| 335 | g_idle_add (load_from_service_complete_idle, g_thread_self ()); |
| 336 | |
| 337 | return lfsd; |
| 338 | } |
| 339 | |
| 340 | static void |
| 341 | start_load_from_service (TreeWindow *w, |
| 342 | DBusGConnection *connection, |
| 343 | const char *service_name) |
| 344 | { |
| 345 | LoadFromServiceData *d; |
| 346 | |
| 347 | d = g_new0 (LoadFromServiceData, 1); |
| 348 | |
| 349 | d->connection = dbus_g_connection_ref (connection); |
| 350 | d->service_name = g_strdup (service_name); |
| 351 | d->error = NULL; |
| 352 | d->node = NULL; |
| 353 | d->window = w; |
| 354 | |
| 355 | g_thread_create (load_from_service_thread_func, d, TRUE, NULL); |
| 356 | } |
| 357 | |
| 358 | static void |
| 359 | tree_window_set_service (TreeWindow *w, |
| 360 | const char *service_name) |
| 361 | { |
| 362 | start_load_from_service (w, w->connection, service_name); |
| 363 | } |
| 364 | |
| 365 | static void |
| 366 | name_combo_changed_callback (GtkComboBox *combo, |
| 367 | TreeWindow *w) |
| 368 | { |
| 369 | GtkTreeIter iter; |
| 370 | |
| 371 | if (gtk_combo_box_get_active_iter (combo, &iter)) |
| 372 | { |
| 373 | GtkTreeModel *model; |
| 374 | char *text; |
| 375 | |
| 376 | model = gtk_combo_box_get_model (combo); |
| 377 | gtk_tree_model_get (model, &iter, 0, &text, -1); |
| 378 | |
| 379 | if (text) |
| 380 | { |
| 381 | tree_window_set_service (w, text); |
| 382 | g_free (text); |
| 383 | } |
| 384 | } |
| 385 | } |
| 386 | |
| 387 | static void |
| 388 | window_closed_callback (GtkWidget *window, |
| 389 | TreeWindow *w) |
| 390 | { |
| 391 | g_assert (window == w->window); |
| 392 | w->window = NULL; |
| 393 | gtk_main_quit (); |
| 394 | } |
| 395 | |
| 396 | static TreeWindow* |
| 397 | tree_window_new (DBusGConnection *connection, |
| 398 | GtkTreeModel *names_model) |
| 399 | { |
| 400 | TreeWindow *w; |
| 401 | GtkWidget *sw; |
| 402 | GtkWidget *vbox; |
| 403 | GtkWidget *hbox; |
| 404 | GtkWidget *combo; |
| 405 | |
| 406 | /* Should use glade, blah */ |
| 407 | |
| 408 | w = g_new0 (TreeWindow, 1); |
| 409 | w->window = gtk_window_new (GTK_WINDOW_TOPLEVEL); |
| 410 | |
| 411 | gtk_window_set_title (GTK_WINDOW (w->window), "D-Bus Viewer"); |
| 412 | gtk_window_set_default_size (GTK_WINDOW (w->window), 400, 500); |
| 413 | |
| 414 | g_signal_connect (w->window, "destroy", G_CALLBACK (window_closed_callback), |
| 415 | w); |
| 416 | gtk_container_set_border_width (GTK_CONTAINER (w->window), 6); |
| 417 | |
| 418 | vbox = gtk_vbox_new (FALSE, 6); |
| 419 | gtk_container_add (GTK_CONTAINER (w->window), vbox); |
| 420 | |
| 421 | /* Create names option menu */ |
| 422 | if (connection) |
| 423 | { |
| 424 | GtkCellRenderer *cell; |
| 425 | |
| 426 | w->connection = connection; |
| 427 | |
| 428 | w->names_model = names_model; |
| 429 | |
| 430 | combo = gtk_combo_box_new_with_model (w->names_model); |
| 431 | |
| 432 | cell = gtk_cell_renderer_text_new (); |
| 433 | gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), cell, TRUE); |
| 434 | gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), cell, |
| 435 | "text", 0, |
| 436 | NULL); |
| 437 | |
| 438 | gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, FALSE, 0); |
| 439 | |
| 440 | g_signal_connect (combo, "changed", |
| 441 | G_CALLBACK (name_combo_changed_callback), |
| 442 | w); |
| 443 | } |
| 444 | |
| 445 | /* Create tree view */ |
| 446 | hbox = gtk_hbox_new (FALSE, 6); |
| 447 | gtk_container_add (GTK_CONTAINER (vbox), hbox); |
| 448 | |
| 449 | sw = gtk_scrolled_window_new (NULL, NULL); |
| 450 | gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), |
| 451 | GTK_POLICY_AUTOMATIC, |
| 452 | GTK_POLICY_AUTOMATIC); |
| 453 | |
| 454 | gtk_box_pack_start (GTK_BOX (hbox), sw, TRUE, TRUE, 0); |
| 455 | |
| 456 | w->treeview = dbus_tree_view_new (); |
| 457 | |
| 458 | gtk_container_add (GTK_CONTAINER (sw), w->treeview); |
| 459 | |
| 460 | /* Show everything */ |
| 461 | gtk_widget_show_all (w->window); |
| 462 | |
| 463 | return w; |
| 464 | } |
| 465 | |
| 466 | static void |
| 467 | usage (int ecode) |
| 468 | { |
| 469 | fprintf (stderr, "dbus-viewer [--version] [--help]\n"); |
| 470 | exit (ecode); |
| 471 | } |
| 472 | |
| 473 | static void |
| 474 | version (void) |
| 475 | { |
| 476 | printf ("D-Bus Message Bus Viewer %s\n" |
| 477 | "Copyright (C) 2003 Red Hat, Inc.\n" |
| 478 | "This is free software; see the source for copying conditions.\n" |
| 479 | "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", |
| 480 | VERSION); |
| 481 | exit (0); |
| 482 | } |
| 483 | |
| 484 | int |
| 485 | main (int argc, char **argv) |
| 486 | { |
| 487 | int i; |
| 488 | GSList *files; |
| 489 | gboolean end_of_args; |
| 490 | GSList *tmp; |
| 491 | gboolean services; |
| 492 | DBusGConnection *connection; |
| 493 | GError *error; |
| 494 | GtkTreeModel *names_model; |
| 495 | |
| 496 | g_thread_init (NULL); |
| 497 | dbus_g_thread_init (); |
| 498 | |
| 499 | bindtextdomain (GETTEXT_PACKAGE, DBUS_LOCALEDIR); |
| 500 | bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); |
| 501 | textdomain (GETTEXT_PACKAGE); |
| 502 | |
| 503 | gtk_init (&argc, &argv); |
| 504 | |
| 505 | services = FALSE; |
| 506 | end_of_args = FALSE; |
| 507 | files = NULL; |
| 508 | i = 1; |
| 509 | while (i < argc) |
| 510 | { |
| 511 | const char *arg = argv[i]; |
| 512 | |
| 513 | if (!end_of_args) |
| 514 | { |
| 515 | if (strcmp (arg, "--help") == 0 || |
| 516 | strcmp (arg, "-h") == 0 || |
| 517 | strcmp (arg, "-?") == 0) |
| 518 | usage (0); |
| 519 | else if (strcmp (arg, "--version") == 0) |
| 520 | version (); |
| 521 | else if (strcmp (arg, "--services") == 0) |
| 522 | services = TRUE; |
| 523 | else if (arg[0] == '-' && |
| 524 | arg[1] == '-' && |
| 525 | arg[2] == '\0') |
| 526 | end_of_args = TRUE; |
| 527 | else if (arg[0] == '-') |
| 528 | { |
| 529 | usage (1); |
| 530 | } |
| 531 | else |
| 532 | { |
| 533 | files = g_slist_prepend (files, (char*) arg); |
| 534 | } |
| 535 | } |
| 536 | else |
| 537 | files = g_slist_prepend (files, (char*) arg); |
| 538 | |
| 539 | ++i; |
| 540 | } |
| 541 | |
| 542 | if (services || files == NULL) |
| 543 | { |
| 544 | error = NULL; |
| 545 | connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error); |
| 546 | if (connection == NULL) |
| 547 | { |
| 548 | g_printerr ("Could not open bus connection: %s\n", |
| 549 | error->message); |
| 550 | g_error_free (error); |
| 551 | exit (1); |
| 552 | } |
| 553 | |
| 554 | g_assert (connection == dbus_g_bus_get (DBUS_BUS_SESSION, NULL)); |
| 555 | |
| 556 | names_model = names_model_new (connection); |
| 557 | } |
| 558 | else |
| 559 | { |
| 560 | connection = NULL; |
| 561 | names_model = NULL; |
| 562 | } |
| 563 | |
| 564 | if (files == NULL) |
| 565 | { |
| 566 | TreeWindow *w; |
| 567 | |
| 568 | w = tree_window_new (connection, names_model); |
| 569 | } |
| 570 | |
| 571 | files = g_slist_reverse (files); |
| 572 | |
| 573 | tmp = files; |
| 574 | while (tmp != NULL) |
| 575 | { |
| 576 | const char *filename; |
| 577 | TreeWindow *w; |
| 578 | |
| 579 | filename = tmp->data; |
| 580 | |
| 581 | if (services) |
| 582 | { |
| 583 | w = tree_window_new (connection, names_model); |
| 584 | tree_window_set_service (w, filename); |
| 585 | } |
| 586 | else |
| 587 | { |
| 588 | NodeInfo *node; |
| 589 | |
| 590 | error = NULL; |
| 591 | node = description_load_from_file (filename, |
| 592 | &error); |
| 593 | |
| 594 | if (node == NULL) |
| 595 | { |
| 596 | g_assert (error != NULL); |
| 597 | show_error_dialog (NULL, NULL, |
| 598 | _("Unable to load \"%s\": %s\n"), |
| 599 | filename, error->message); |
| 600 | g_error_free (error); |
| 601 | } |
| 602 | else |
| 603 | { |
| 604 | w = tree_window_new (connection, names_model); |
| 605 | tree_window_set_node (w, node); |
| 606 | node_info_unref (node); |
| 607 | } |
| 608 | } |
| 609 | |
| 610 | tmp = tmp->next; |
| 611 | } |
| 612 | |
| 613 | gtk_main (); |
| 614 | |
| 615 | return 0; |
| 616 | } |
| 617 | |