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 618