1 /* bug-buddy bug submitting program
3 * Copyright (C) 1999 - 2001 Jacob Berkman
4 * Copyright 2000, 2001 Ximian, Inc.
6 * Author: jacob berkman <jacob@bug-buddy.org>
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of version 2 of the GNU General Public
10 * License as published by the Free Software Foundation.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
24 #include "eds-buddy.h"
25 #include "gdb-buddy.h"
27 #include "bug-buddy.h"
28 #include "distribution.h"
30 #include "forbidden-words.h"
35 #include <sys/utsname.h>
36 #include <sys/types.h>
42 #include <glib/gstdio.h>
43 #include <glib/gi18n.h>
46 #include <gdk-pixbuf/gdk-pixbuf.h>
49 #include <gdk/gdkkeysyms.h>
50 #include <bonobo-activation/bonobo-activation.h>
52 #include <libxml/tree.h>
53 #include <libxml/parser.h>
55 #include <gconf/gconf-client.h>
57 #include <libsoup/soup.h>
59 #define USE_PROXY_KEY "/system/http_proxy/use_http_proxy"
60 #define PROXY_HOST_KEY "/system/http_proxy/host"
61 #define PROXY_PORT_KEY "/system/http_proxy/port"
62 #define USE_PROXY_AUTH "/system/http_proxy/use_authentication"
63 #define PROXY_USER "/system/http_proxy/authentication_user"
64 #define PROXY_PASSWORD "/system/http_proxy/authentication_password"
65 #define ACCESSIBILITY_KEY "/desktop/gnome/interface/accessibility"
66 #define GTK_THEME_KEY "/desktop/gnome/interface/gtk_theme"
67 #define ICON_THEME_KEY "/desktop/gnome/interface/icon_theme"
68 #define GTK_MODULES_KEY "/apps/gnome_settings_daemon/gtk-modules"
69 #define DESKTOP_IS_HOME_DIR "/apps/nautilus/preferences/desktop_is_home_dir"
70 #define MIN_REPORT_DETAILS_CHARS 10
72 static GOptionData gopt_data
;
73 static int bug_count
= 0;
74 static GHashTable
*apps
= NULL
;
77 show_version_cb (const char *option_name
,
82 g_print ("%s %s\n", _("GNOME Bug Buddy"), VERSION
);
89 static const GOptionEntry options
[] = {
90 { "package", '\0', 0, G_OPTION_ARG_STRING
, &gopt_data
.package
, N_("Package containing the program"), N_("PACKAGE") },
91 { "appname", '\0', 0, G_OPTION_ARG_FILENAME
, &gopt_data
.app_file
, N_("File name of crashed program"), N_("FILE") },
92 { "pid", '\0', 0, G_OPTION_ARG_INT
, &gopt_data
.pid
, N_("PID of crashed program"), N_("PID") },
93 { "include", '\0', 0, G_OPTION_ARG_FILENAME
, &gopt_data
.include_file
, N_("Text file to include in the report"), N_("FILE") },
94 { "unlink-tempfile", '\0', 0, G_OPTION_ARG_NONE
, &gopt_data
.own_file
, N_("Delete the included file after reporting"),NULL
},
95 { "version", '\0', G_OPTION_FLAG_NO_ARG
, G_OPTION_ARG_CALLBACK
, show_version_cb
, NULL
, NULL
},
99 static void fill_stderr_info (GtkBuilder
*ui
);
100 static void fill_custom_info (BugzillaApplication
*app
, GtkBuilder
*ui
);
101 static void close_callback (GtkWidget
*widget
, gpointer user_data
);
102 static void bug_buddy_quit (GtkBuilder
*ui
);
103 static void fill_include_file (char *filename
, gboolean own_file
, GtkBuilder
*ui
);
107 buddy_error (GtkWidget
*parent
, const char *msg
, ...)
114 /* No va_list version of dialog_new, construct the string ourselves. */
115 va_start (args
, msg
);
116 s
= g_strdup_vprintf (msg
, args
);
119 w
= gtk_message_dialog_new (GTK_WINDOW (parent
),
126 gtk_dialog_set_default_response (d
, GTK_RESPONSE_OK
);
128 gtk_widget_destroy (w
);
133 lock_text (GtkBuilder
*ui
)
135 GtkTextView
*text_view
;
136 GtkTextBuffer
*buffer
;
139 static GtkTextTag
*tag
= NULL
;
142 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
143 buffer
= gtk_text_view_get_buffer (text_view
);
144 gtk_text_buffer_get_start_iter (buffer
, &start
);
145 gtk_text_buffer_get_end_iter (buffer
, &end
);
146 text
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
149 GtkStyle
*style
= gtk_widget_get_style (GTK_WIDGET (text_view
));
150 tag
= gtk_text_buffer_create_tag (buffer
, "lock_tag",
152 /* I don't like how it looks like dimming also fg
153 "foreground-gdk", &style->fg[GTK_STATE_INSENSITIVE], */
154 "background-gdk", &style
->bg
[GTK_STATE_INSENSITIVE
],
158 if (gtk_text_iter_forward_search (&start
, "Backtrace was generated from",
159 GTK_TEXT_SEARCH_TEXT_ONLY
,
161 gtk_text_iter_forward_line (&end
);
162 gtk_text_buffer_apply_tag_by_name (buffer
, "lock_tag", &start
, &end
);
168 search_forbidden_words (GtkBuilder
*ui
)
170 GtkTextView
*text_view
;
171 GtkTextBuffer
*buffer
;
173 gboolean found
= FALSE
;
174 static GtkTextTag
*tag
= NULL
;
176 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
177 buffer
= gtk_text_view_get_buffer (text_view
);
179 tag
= gtk_text_buffer_create_tag (buffer
, "forbbiden_tag",
180 "foreground", "white",
181 "background", "blue",
185 for (i
= 0; forbidden_words
[i
]; i
++) {
189 gtk_text_buffer_get_start_iter (buffer
, &start
);
190 while (gtk_text_iter_forward_search (&start
, forbidden_words
[i
],
191 GTK_TEXT_SEARCH_TEXT_ONLY
,
192 &start
, &end
, NULL
)) {
193 gtk_text_buffer_apply_tag_by_name (buffer
, "forbbiden_tag", &start
, &end
);
205 copy_review (GtkWidget
*button
, gpointer data
)
207 GtkTextView
*text_view
;
208 GtkTextBuffer
*buffer
;
211 GtkBuilder
*ui
= (GtkBuilder
*) data
;
213 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
214 buffer
= gtk_text_view_get_buffer (text_view
);
215 gtk_text_buffer_get_start_iter (buffer
, &start
);
216 gtk_text_buffer_get_end_iter (buffer
, &end
);
217 gtk_text_buffer_select_range (buffer
, &start
, &end
);
218 gtk_text_buffer_copy_clipboard (buffer
, gtk_clipboard_get (GDK_NONE
));
223 edit_review (GtkWidget
*button
, gpointer data
)
225 GtkTextView
*text_view
;
226 GtkBuilder
*ui
= (GtkBuilder
*) data
;
227 gboolean editable
= gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button
));
229 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
230 gtk_text_view_set_editable (text_view
, editable
);
238 close_review (GtkWidget
*button
, gpointer data
)
240 GtkWidget
*review_dialog
= GTK_WIDGET (data
);
242 gtk_widget_hide (review_dialog
);
246 delete_review (GtkWidget
*widget
, GdkEvent
*event
, gpointer user_data
)
248 gtk_widget_hide (widget
);
250 return TRUE
; /* don't destroy */
254 show_review (GtkWidget
*button
, gpointer data
)
256 GtkWidget
*review_dialog
, *main_window
;
257 GtkWidget
*edit
, *copy
, *close
;
258 GtkBuilder
*ui
= (GtkBuilder
*) data
;
259 static gboolean initialized
= FALSE
;
262 review_dialog
= GTK_WIDGET (gtk_builder_get_object (ui
, "review-dialog"));
263 main_window
= GTK_WIDGET (gtk_builder_get_object (ui
, "main-window"));
264 copy
= GTK_WIDGET (gtk_builder_get_object (ui
, "copy-review-button"));
265 edit
= GTK_WIDGET (gtk_builder_get_object (ui
, "edit-review-button"));
266 close
= GTK_WIDGET (gtk_builder_get_object (ui
, "close-review-button"));
268 gtk_window_set_transient_for (GTK_WINDOW (review_dialog
), GTK_WINDOW (main_window
));
270 g_signal_connect (G_OBJECT (copy
), "clicked", G_CALLBACK (copy_review
), ui
);
271 g_signal_connect (G_OBJECT (edit
), "toggled", G_CALLBACK (edit_review
), ui
);
272 g_signal_connect (G_OBJECT (close
), "clicked", G_CALLBACK (close_review
), review_dialog
);
273 g_signal_connect (G_OBJECT (review_dialog
), "delete-event", G_CALLBACK (delete_review
), NULL
);
280 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui
, "review-dialog")));
284 static GnomeVersionInfo
*
285 get_gnome_version_info (void)
287 GnomeVersionInfo
*version
;
291 guchar
*platform
, *minor
, *micro
, *distributor
, *date
;
293 version
= g_new0 (GnomeVersionInfo
, 1);
295 xml_file
= g_build_filename (DATADIR
, "gnome-about/gnome-version.xml", NULL
);
297 doc
= xmlParseFile (xml_file
);
303 platform
= minor
= micro
= distributor
= date
= NULL
;
305 for (node
= xmlDocGetRootElement (doc
)->children
; node
; node
= node
->next
) {
306 if (!strcmp ((char *)node
->name
, "platform"))
307 platform
= xmlNodeGetContent (node
);
308 else if (!strcmp ((char *)node
->name
, "minor"))
309 minor
= xmlNodeGetContent (node
);
310 else if (!strcmp ((char *)node
->name
, "micro"))
311 micro
= xmlNodeGetContent (node
);
312 else if (!strcmp ((char *)node
->name
, "distributor"))
313 distributor
= xmlNodeGetContent (node
);
314 else if (!strcmp ((char *)node
->name
, "date"))
315 date
= xmlNodeGetContent (node
);
318 if (platform
&& minor
&& micro
)
319 version
->gnome_platform
= g_strdup_printf ("%s.%s.%s", platform
, minor
, micro
);
321 if (distributor
&& *distributor
)
322 version
->gnome_distributor
= g_strdup ((char *)distributor
);
325 version
->gnome_date
= g_strdup ((char *)date
);
330 xmlFree (distributor
);
339 update_progress_bar (gpointer data
)
341 GtkProgressBar
*pbar
= GTK_PROGRESS_BAR (data
);
343 gtk_progress_bar_pulse (pbar
);
349 save_email (const char *email
)
351 GConfClient
*conf_client
;
353 conf_client
= gconf_client_get_default ();
354 gconf_client_set_string (conf_client
, "/apps/bug-buddy/email_address", email
, NULL
);
355 g_object_unref (conf_client
);
359 copy_link_item_activated_cb (GtkMenuItem
*menu_item
,
362 GtkClipboard
*clipboard
;
364 clipboard
= gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
);
365 gtk_clipboard_set_text (clipboard
, link
, -1);
369 build_link_menu (const gchar
*link
)
375 menu
= gtk_menu_new ();
376 item
= gtk_image_menu_item_new_with_mnemonic (_("Copy _Link Address"));
377 image
= gtk_image_new_from_stock (GTK_STOCK_COPY
, GTK_ICON_SIZE_MENU
);
378 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item
), image
);
379 gtk_widget_show_all (item
);
380 g_signal_connect (item
, "activate",
381 G_CALLBACK (copy_link_item_activated_cb
), (char *) link
);
382 gtk_menu_shell_append (GTK_MENU_SHELL (menu
), item
);
388 link_button_press_event_cb (GtkWidget
*widget
,
389 GdkEventButton
*event
,
392 const gchar
*link
= gtk_link_button_get_uri (GTK_LINK_BUTTON (widget
));
394 if (event
->type
== GDK_BUTTON_PRESS
&& event
->button
== 3) {
397 popup
= build_link_menu (link
);
398 gtk_menu_popup (GTK_MENU (popup
), NULL
, NULL
, NULL
, NULL
,
399 event
->button
, event
->time
);
408 link_callback (GtkLinkButton
*button
, gpointer user_data
)
410 const gchar
*link
= gtk_link_button_get_uri (button
);
411 GdkAppLaunchContext
*context
;
413 context
= gdk_app_launch_context_new ();
414 gdk_app_launch_context_set_screen (context
,
415 gtk_widget_get_screen (GTK_WIDGET (button
)));
416 gdk_app_launch_context_set_timestamp (context
,
417 gtk_get_current_event_time ());
419 if (!g_app_info_launch_default_for_uri (link
,
420 G_APP_LAUNCH_CONTEXT (context
),
425 text
= g_markup_printf_escaped (_("Bug Buddy was unable to display the link \"%s\"\n"), link
);
426 buddy_error (NULL
, text
);
430 g_object_unref (context
);
436 save_to_file (const gchar
*filename
, const gchar
*text
)
438 GError
*error
= NULL
;
440 if (!g_file_set_contents (filename
, text
, -1, &error
)) {
441 g_warning ("Unable to save document %s: %s\n", filename
, error
->message
);
442 g_error_free (error
);
448 network_error (SoupMessage
*msg
, GtkBuilder
*ui
)
454 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "progressbar")));
456 dialog
= gtk_message_dialog_new_with_markup (NULL
,
460 _("There was a network error while sending the report. "
461 "Do you want to save this report and send it later?"));
462 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog
),
463 "%s", _("Please ensure that your Internet connection is active "
464 "and working correctly."));
465 res
= gtk_dialog_run (GTK_DIALOG (dialog
));
466 gtk_widget_destroy (dialog
);
468 if (res
== GTK_RESPONSE_YES
) {
472 dirname
= g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
473 if (!g_file_test (dirname
, G_FILE_TEST_IS_DIR
)) {
474 g_mkdir_with_parents (dirname
, 0755);
477 filename
= g_strdup_printf ("%s/%ld", dirname
, (long)time (NULL
));
479 save_to_file (filename
, msg
->request_body
->data
);
490 remove_pending_reports (void)
494 GError
*error
= NULL
;
496 dirname
= g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
497 dir
= g_dir_open (dirname
, 0, &error
);
499 const char *name
= g_dir_read_name (dir
);
501 char *path
= g_strdup_printf ("%s/%s", dirname
, name
);
504 name
= g_dir_read_name (dir
);
514 all_sent (GtkBuilder
*ui
)
516 GtkWidget
*close_button
;
518 /* hide the progressbar */
519 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "progressbar")));
521 close_button
= GTK_WIDGET (gtk_builder_get_object (ui
, "close-button"));
522 gtk_widget_show (close_button
);
526 previous_sent (SoupSession
*session
, SoupMessage
*msg
, GtkBuilder
*ui
)
528 if (--bug_count
== 0) {
535 bug_sent (SoupSession
*session
, SoupMessage
*msg
, GtkBuilder
*ui
)
545 GtkWidget
*urlbutton
;
546 GtkRequisition requisition
;
549 button
= GTK_WIDGET (gtk_builder_get_object (ui
, "close-button"));
550 gtk_button_set_label (GTK_BUTTON (button
), _("_Close"));
551 gtk_button_set_use_underline (GTK_BUTTON (button
), TRUE
);
553 image
= gtk_image_new_from_stock (GTK_STOCK_CLOSE
, GTK_ICON_SIZE_BUTTON
),
554 gtk_button_set_image (GTK_BUTTON (button
), image
);
556 if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg
->status_code
)) {
557 network_error (msg
, ui
);
559 remove_pending_reports ();
562 /* parse the XML-RPC response */
563 response
= bugzilla_parse_response (msg
, &err
);
564 if (response
!= NULL
) {
565 bugid
= strtol (response
, &tmp
, 10);
566 GtkWidget
*main_vbox
;
568 /* we need a reference to the vbox containing the text so that we
569 * can add a GtkLinkButton to the bug report */
570 main_vbox
= GTK_WIDGET (gtk_builder_get_object (ui
, "main-vbox"));
572 if (response
== tmp
) {
574 url
= strstr (response
, "ViewURL=");
576 url
+= strlen ("ViewURL=");
580 text
= g_strdup (url
);
582 text
= g_strdup_printf ("http://bugzilla.gnome.org/show_bug.cgi?id=%ld", bugid
);
584 /* create a clickable link to the bug report */
585 urlbutton
= gtk_link_button_new (text
);
586 g_signal_connect (G_OBJECT (urlbutton
), "clicked", G_CALLBACK (link_callback
), NULL
);
587 g_signal_connect (G_OBJECT (urlbutton
), "button-press-event",
588 G_CALLBACK (link_button_press_event_cb
), NULL
);
589 gtk_box_pack_end (GTK_BOX (main_vbox
), urlbutton
, FALSE
, FALSE
, 0);
591 gtk_widget_show (urlbutton
);
594 text
= g_markup_printf_escaped (_("A bug report detailing your software crash has been sent to GNOME. "
595 "This information will allow the developers to understand the cause "
596 "of the crash and prepare a solution for it.\n\n"
597 "You may be contacted by a GNOME developer if more details are "
598 "required about the crash.\n\n"
599 "You can view your bug report and follow its progress with this URL:\n")) ;
601 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui
, "main-text")), text
);
603 save_email (gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui
, "email-entry"))));
605 errmsg
= _("Bug Buddy encountered an error while submitting your report "
606 "to the Bugzilla server. Details of the error are included below.\n\n");
608 if (err
&& err
->domain
== SOUP_XMLRPC_FAULT
) {
609 /* see http://cvs.gnome.org/viewcvs/bugzilla-newer/Bugzilla/RPC.pm?view=markup */
610 if (err
->message
== NULL
) {
611 text
= g_strdup_printf (_("Bugzilla reported an error when trying to process your "
612 "request, but was unable to parse the response."));
613 } else if (g_str_equal (err
->message
, "invalid_username")) {
614 text
= g_strdup_printf (_("The email address you provided is not valid."));
615 } else if (g_str_equal (err
->message
, "account_disabled")) {
616 text
= g_strdup_printf (_("The account associated with the email address "
617 "provided has been disabled."));
618 } else if (g_str_equal (err
->message
, "product_doesnt_exist")) {
619 text
= g_strdup_printf (_("The product specified doesn't exist or has been "
620 "renamed. Please upgrade to the latest version."));
621 } else if (g_str_equal (err
->message
, "component_not_valid")) {
622 text
= g_strdup_printf (_("The component specified doesn't exist or has been "
623 "renamed. Please upgrade to the latest version."));
624 } else if (g_str_equal (err
->message
, "require_summary")) {
625 text
= g_strdup_printf (_("The summary is required in your bug report. "
626 "This should not happen with the latest Bug Buddy."));
627 } else if (g_str_equal (err
->message
, "description_required")) {
628 text
= g_strdup_printf (_("The description is required in your bug report. "
629 "This should not happen with the latest Bug Buddy."));
631 text
= g_strdup_printf (_("The fault code returned by Bugzilla is not recognized. "
632 "Please report the following information to "
633 "bugzilla.gnome.org manually:\n\n%s"), err
->message
);
637 case BUGZILLA_ERROR_RECV_BAD_STATUS
:
638 text
= g_strdup_printf (_("Server returned bad state. This is most likely a server "
639 "issue and should be reported to bugmaster@gnome.org\n\n%s"),
642 case BUGZILLA_ERROR_RECV_PARSE_FAILED
:
643 text
= g_strdup_printf (_("Failed to parse the XML-RPC response. Response follows:\n\n%s"),
647 text
= g_strdup_printf (_("An unknown error occurred. This is most likely a problem with "
648 "Bug Buddy. Please report this problem manually at bugzilla."
653 str
= g_strconcat (errmsg
, text
, NULL
);
654 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui
, "main-text")), str
);
662 if (--bug_count
== 0) {
666 gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui
, "main-window")), &requisition
);
667 gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui
, "main-window")),
668 requisition
.width
, requisition
.height
);
676 set_proxy (SoupSession
*session
)
678 GConfClient
*gconf_client
;
683 char *username
= NULL
;
684 char *password
= NULL
;
687 gconf_client
= gconf_client_get_default ();
689 if (gconf_client_get_bool (gconf_client
, USE_PROXY_KEY
, NULL
) == FALSE
) {
690 g_object_unref (gconf_client
);
694 host
= gconf_client_get_string (gconf_client
, PROXY_HOST_KEY
, NULL
);
696 g_object_unref (gconf_client
);
699 port
= gconf_client_get_int (gconf_client
, PROXY_PORT_KEY
, NULL
);
703 if (gconf_client_get_bool (gconf_client
, USE_PROXY_AUTH
, NULL
)) {
704 username
= gconf_client_get_string (gconf_client
, PROXY_USER
, NULL
);
705 password
= gconf_client_get_string (gconf_client
, PROXY_PASSWORD
, NULL
);
708 if (username
&& password
)
709 proxy_uri
= g_strdup_printf ("http://%s:%s@%s:%d", username
, password
, host
, port
);
711 proxy_uri
= g_strdup_printf ("http://%s:%d", host
, port
);
713 uri
= soup_uri_new (proxy_uri
);
714 g_object_set (G_OBJECT (session
), "proxy-uri", uri
, NULL
);
721 g_object_unref (gconf_client
);
727 create_report_title (BugzillaApplication
*app
, int type
, const char *description
)
734 tmp
= g_malloc0 (256); /* This should be safe enough for 24 UTF-8 chars.
735 * anyway, I miss a g_utf8_strndup :) */
736 size
= g_utf8_strlen (description
, -1);
738 g_utf8_strncpy (tmp
, description
, 24);
740 g_utf8_strncpy (tmp
, description
, size
);
744 if (type
== BUG_TYPE_CRASH
) {
745 title
= g_strdup_printf ("crash in %s: %s%s", app
->cname
,
746 tmp
? tmp
: "empty description",
747 (tmp
&& size
> 24) ? "..." : "");
749 title
= g_strdup_printf ("%s: %s%s", app
->cname
,
750 tmp
? tmp
: "empty description",
751 (tmp
&& size
> 24) ? "..." : "");
759 send_report (BugzillaApplication
*app
, GnomeVersionInfo
*gnome_version
, GtkBuilder
*ui
)
761 GtkTextView
*text_view
;
762 GtkTextBuffer
*buffer
;
771 SoupSession
*session
;
772 SoupMessage
*message
;
775 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "pending-reports-check")));
777 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
778 buffer
= gtk_text_view_get_buffer (text_view
);
779 gtk_text_buffer_get_start_iter (buffer
, &start
);
780 gtk_text_buffer_get_end_iter (buffer
, &end
);
781 gdb_text
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
783 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "details-view"));
784 buffer
= gtk_text_view_get_buffer (text_view
);
785 gtk_text_buffer_get_start_iter (buffer
, &start
);
786 gtk_text_buffer_get_end_iter (buffer
, &end
);
787 details_text
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
789 type
= GPOINTER_TO_INT (g_object_get_data (G_OBJECT (ui
), "type"));
790 final_text
= g_strdup_printf ("%s%s\n\n\n%s",
791 type
== BUG_TYPE_CRASH
? "What were you doing when the application crashed?\n" : "",
792 details_text
!= NULL
? details_text
: "",
793 gdb_text
!= NULL
? gdb_text
: "<empty backtrace>");
795 email
= gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui
, "email-entry")));
796 title
= create_report_title (app
, type
, details_text
);
798 message
= bugzilla_create_report (app
, type
, gnome_version
, email
, title
, final_text
, ui
, &err
);
799 if (message
== NULL
) {
803 text
= g_strdup_printf (_("Unable to create the bug report: %s\n"), err
->message
);
805 text
= g_strdup_printf (_("There was an error creating the bug report\n"));
808 buddy_error (NULL
, text
);
811 g_free (details_text
);
818 session
= soup_session_async_new ();
822 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ui
, "pending-reports-check")))) {
825 GError
*error
= NULL
;
827 dirname
= g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
828 dir
= g_dir_open (dirname
, 0, &error
);
830 const char *name
= g_dir_read_name (dir
);
831 while (name
!= NULL
) {
836 path
= g_strdup_printf ("%s/%s", dirname
, name
);
837 if (g_file_get_contents (path
, &contents
, &length
, NULL
)) {
839 msg
= soup_message_new ("POST", "http://bugzilla.gnome.org/bugbuddy.cgi");
840 soup_message_set_request (msg
, "text/xml",
844 soup_session_queue_message (session
, SOUP_MESSAGE (msg
),
845 (SoupSessionCallback
)previous_sent
, ui
);
848 name
= g_dir_read_name (dir
);
857 soup_session_queue_message (session
, message
,
858 (SoupSessionCallback
)bug_sent
, ui
);
860 g_free (details_text
);
864 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui
, "progressbar")),
869 /* A local part is valid if it is one or more valid characters. */
871 email_local_part_is_valid (const char *local_part
)
873 const char *character
;
878 for (character
= local_part
; *character
; character
++) {
879 /* RFC 3696 says *any* printable ASCII character can
880 * appear in local-part, subject to quoting rules. */
881 if (g_ascii_isprint (*character
))
884 /* Not valid character, not valid local part. */
891 /* A domain label is valid if it is one or more valid characters. */
893 email_domain_label_is_valid (const char *domain_label
)
895 const char *character
;
898 /* Validate each character, whilst measuring length, i. */
899 for (i
= 0; *(character
= domain_label
+ i
); i
++) {
901 /* If character is alphanumeric it is valid. */
902 if (g_ascii_isalnum (*character
))
905 /* If it's a hyphen, it's also valid. */
906 if (*character
== '-')
909 /* Anything else is invalid */
913 /* Labels must be between 1 and 63 characters long */
914 if (i
< 1 || i
> 63) {
921 /* A domain is valid if it is one or more valid, dot-separated labels. */
923 email_domain_is_valid (const char *domain
)
927 gboolean retval
= FALSE
;
929 /* If there is no domain, there are no domain labels and the domain is
934 /* Split the domain on the dot to validate labels. */
935 labels
= g_strsplit (domain
, ".", 0);
936 if (g_strv_length (labels
) == 1) {
937 /* the domain doesn't contain any dot: it's not valid */
941 for (this_label
= labels
; *this_label
; this_label
++) {
942 if (!email_domain_label_is_valid (*this_label
)) {
954 /* Check for *simple* email addresses of the form user@host, with checks
955 * in characters used, and sanity checks on the form of host.
957 /* FIXME: Should we provide a useful error message? */
959 email_is_valid (const char *address
)
966 /* Split on the *last* '@' character: */
967 at_sign
= strrchr (address
, '@');
972 local_part
= g_strndup (address
, at_sign
- address
);
973 domain
= g_strdup (at_sign
+ 1);
975 /* Check each part is valid */
976 is_valid
= email_local_part_is_valid (local_part
)
977 && email_domain_is_valid (domain
);
986 check_email (GtkEditable
*editable
, gpointer data
)
989 GtkBuilder
*ui
= (GtkBuilder
*) data
;
991 email
= gtk_entry_get_text (GTK_ENTRY (editable
));
992 gtk_widget_set_sensitive (GTK_WIDGET (gtk_builder_get_object (ui
, "send-button")),
993 email_is_valid (email
));
998 on_send_clicked (GtkWidget
*button
, gpointer data
)
1000 BugzillaApplication
*app
;
1001 GnomeVersionInfo
*gnome_version
;
1002 GtkRequisition requisition
;
1003 GtkBuilder
*ui
= (GtkBuilder
*) data
;
1007 app
= g_object_get_data (G_OBJECT (ui
), "app");
1008 gnome_version
= g_object_get_data (G_OBJECT (ui
), "gnome-version");
1010 details
= GTK_WIDGET (gtk_builder_get_object (ui
, "details-view"));
1011 i
= gtk_text_buffer_get_char_count (
1012 gtk_text_view_get_buffer (GTK_TEXT_VIEW (details
)));
1014 if (i
< MIN_REPORT_DETAILS_CHARS
) {
1021 dialog
= gtk_message_dialog_new (NULL
,
1025 _("The description you provided for the "
1026 "crash is very short. Are you sure you want "
1029 /* Secondary text */
1030 gtk_message_dialog_format_secondary_text
1031 (GTK_MESSAGE_DIALOG (dialog
),
1032 _("A short description is probably not of much help "
1033 "to the developers investigating your report. "
1034 "If you provide a better one, for instance "
1035 "specifying a way to reproduce the crash, the "
1036 "issue can be more easily resolved."));
1039 button
= gtk_dialog_add_button (GTK_DIALOG (dialog
),
1040 _("_Review description"),
1041 GTK_RESPONSE_CANCEL
);
1042 icon
= gtk_image_new_from_stock
1043 (GTK_STOCK_EDIT
, GTK_ICON_SIZE_BUTTON
);
1044 gtk_button_set_image (GTK_BUTTON (button
), icon
);
1045 gtk_widget_show (button
);
1047 /* Send anyway button */
1048 button
= gtk_dialog_add_button (GTK_DIALOG (dialog
),
1051 icon
= gtk_image_new_from_stock
1052 (GTK_STOCK_OK
, GTK_ICON_SIZE_BUTTON
);
1053 gtk_button_set_image (GTK_BUTTON (button
), icon
);
1054 gtk_widget_show (button
);
1056 gtk_dialog_set_default_response (GTK_DIALOG (dialog
),
1059 i
= gtk_dialog_run (GTK_DIALOG (dialog
));
1061 gtk_widget_destroy (dialog
);
1063 if (i
!= GTK_RESPONSE_OK
)
1067 /* hide the send button immediately so that the user can't click
1068 * it more than once (this will create multiple bugs).
1070 gtk_widget_hide (GTK_WIDGET (
1071 gtk_builder_get_object (ui
, "send-button")));
1073 gtk_widget_show (GTK_WIDGET (
1074 gtk_builder_get_object (ui
, "progressbar")));
1075 gtk_widget_hide (GTK_WIDGET (
1076 gtk_builder_get_object (ui
, "final-box")));
1077 gtk_widget_hide (GTK_WIDGET (
1078 gtk_builder_get_object (ui
, "review-box")));
1080 gtk_widget_size_request (GTK_WIDGET (
1081 gtk_builder_get_object (ui
, "main-window")),
1084 gtk_window_resize (GTK_WINDOW (
1085 gtk_builder_get_object (ui
, "main-window")),
1086 requisition
.width
, requisition
.height
);
1088 send_report (app
, gnome_version
, ui
);
1093 gdb_insert_text (const gchar
*stacktrace
, GtkBuilder
*ui
)
1095 GtkTextView
*text_view
;
1097 GtkTextBuffer
*buffer
;
1099 /* FIXME: These strings are gdb specific, we should add here also dbx */
1100 const char *bt_step1
= "#1";
1101 const char *bt_step2
= "#2";
1102 const char *bt_step3
= "#3";
1104 if (!g_strrstr (stacktrace
, bt_step1
) &&
1105 !g_strrstr (stacktrace
, bt_step2
) &&
1106 !g_strrstr (stacktrace
, bt_step3
)) {
1112 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
1113 buffer
= gtk_text_view_get_buffer (text_view
);
1114 gtk_text_buffer_get_end_iter (buffer
, &end
);
1116 /* add the stacktrace to the GtkTextView */
1117 gtk_text_buffer_insert (buffer
, &end
, stacktrace
, strlen (stacktrace
));
1123 show_pending_checkbox_if_pending (GtkBuilder
*ui
)
1128 dirname
= g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
1129 if (g_file_test (dirname
, G_FILE_TEST_IS_DIR
)) {
1130 check
= GTK_WIDGET (gtk_builder_get_object (ui
, "pending-reports-check"));
1131 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check
), TRUE
);
1132 gtk_widget_show (check
);
1138 create_debuginfo_link (void)
1140 GtkWidget
*urlbutton
;
1142 /* create a clickable link to the bug report */
1143 urlbutton
= gtk_link_button_new_with_label ("http://live.gnome.org/GettingTraces/DistroSpecificInstructions",
1144 /* Translators: This is the hyperlink which takes to http://live.gnome.org/GettingTraces/DistroSpecificInstructions
1145 * page. Please also mention that the page is in English */
1146 _("Getting useful crash reports"));
1147 g_signal_connect (G_OBJECT (urlbutton
), "clicked", G_CALLBACK (link_callback
), NULL
);
1153 useless_finished (GtkBuilder
*ui
)
1156 GtkWidget
*button
, *image
, *main_vbox
, *urlbutton
;
1157 BugzillaApplication
*app
;
1160 app
= g_object_get_data (G_OBJECT (ui
), "app");
1162 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "progressbar")));
1164 label_text
= g_markup_printf_escaped (_("The application %s crashed. The bug reporting tool was "
1165 "unable to collect enough information about the crash to be "
1166 "useful to the developers.\n\n"
1167 "In order to submit useful reports, please consider installing "
1168 "debug packages for your distribution.\n"
1169 "Click the link below to get information about how to install "
1170 "these packages:\n"),
1172 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui
, "main-text")),
1175 main_vbox
= GTK_WIDGET (gtk_builder_get_object (ui
, "main-vbox"));
1176 urlbutton
= create_debuginfo_link ();
1177 gtk_box_pack_end (GTK_BOX (main_vbox
), urlbutton
, FALSE
, FALSE
, 0);
1179 gtk_widget_show (urlbutton
);
1181 g_free (label_text
);
1183 button
= GTK_WIDGET (gtk_builder_get_object (ui
, "close-button"));
1184 gtk_button_set_label (GTK_BUTTON (button
), _("_Close"));
1185 gtk_button_set_use_underline (GTK_BUTTON (button
), TRUE
);
1187 image
= gtk_image_new_from_stock (GTK_STOCK_CLOSE
, GTK_ICON_SIZE_BUTTON
),
1188 gtk_button_set_image (GTK_BUTTON (button
), image
);
1190 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "email-entry")));
1195 known_app_finished (GtkBuilder
*ui
)
1197 BugzillaApplication
*app
;
1198 GtkWidget
*email_entry
;
1200 char *default_email
;
1201 char *lang_note
, *label_text
, *s
;
1202 const char *en_lang_note
= N_("\n\nPlease write your report in English, if possible.");
1204 app
= g_object_get_data (G_OBJECT (ui
), "app");
1206 fill_custom_info (app
, ui
);
1207 fill_stderr_info (ui
);
1209 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui
, "final-box")));
1210 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui
, "send-button")));
1211 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "progressbar")));
1213 lang_note
= gettext (en_lang_note
);
1215 label_text
= g_strconcat (_("Information about the %s application crash has been successfully collected. "
1216 "Please provide some more details about what you were doing when "
1217 "the application crashed.\n\n"
1219 "A valid email address is required. This will allow the developers to "
1220 "contact you for more information if necessary."),
1221 strcmp (lang_note
, en_lang_note
) ? lang_note
: NULL
,
1224 s
= g_markup_printf_escaped (label_text
, app
->name
);
1226 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui
, "main-text")),
1230 g_free (label_text
);
1232 show_pending_checkbox_if_pending (ui
);
1234 button
= GTK_WIDGET (gtk_builder_get_object (ui
, "send-button"));
1235 g_signal_connect (button
, "clicked",
1236 G_CALLBACK (on_send_clicked
), ui
);
1238 email_entry
= GTK_WIDGET (gtk_builder_get_object (ui
, "email-entry"));
1239 g_signal_connect (email_entry
, "changed", G_CALLBACK (check_email
), ui
);
1241 default_email
= get_default_user_email ();
1243 if (default_email
!= NULL
) {
1244 gtk_entry_set_text (GTK_ENTRY (email_entry
), default_email
);
1245 g_free (default_email
);
1247 gtk_widget_set_sensitive (button
, FALSE
);
1250 if (search_forbidden_words (ui
)) {
1251 char *review_text
= g_markup_printf_escaped ("<small><i><span weight=\"bold\">%s</span> %s</i></small>",
1253 _("Some sensitive data is likely present in the crash details. "
1254 "Please review and edit the information if you are concerned "
1255 "about transmitting passwords or other sensitive data."));
1256 gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui
, "review-label")), review_text
);
1258 g_signal_connect (gtk_builder_get_object (ui
, "review-button"), "clicked", G_CALLBACK (show_review
), ui
);
1259 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui
, "review-box")));
1261 gtk_widget_grab_focus (GTK_WIDGET (gtk_builder_get_object (ui
, "details-view")));
1265 gdb_finished (const gchar
*stacktrace
, gpointer data
)
1267 GtkBuilder
*ui
= (GtkBuilder
*) data
;
1269 if (gdb_insert_text (stacktrace
, ui
)) {
1270 known_app_finished (ui
);
1272 useless_finished (ui
);
1279 on_save_clicked (GtkWidget
*button
, gpointer user_data
)
1281 GtkBuilder
*ui
= (GtkBuilder
*)user_data
;
1283 const char *desktop
;
1285 gboolean desktop_is_home_dir
, saved
;
1286 GConfClient
*gconf_client
;
1290 dialog
= gtk_file_chooser_dialog_new (_("Save File"),
1291 GTK_WINDOW (gtk_builder_get_object (ui
, "main-window")),
1292 GTK_FILE_CHOOSER_ACTION_SAVE
,
1293 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
,
1294 GTK_STOCK_SAVE
, GTK_RESPONSE_ACCEPT
,
1297 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog
), TRUE
);
1299 gconf_client
= gconf_client_get_default ();
1300 desktop_is_home_dir
= gconf_client_get_bool (gconf_client
, DESKTOP_IS_HOME_DIR
, NULL
);
1301 g_object_unref (gconf_client
);
1303 if (desktop_is_home_dir
)
1304 desktop
= g_get_home_dir();
1306 desktop
= g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP
);
1308 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog
), desktop
);
1310 filename
= g_strconcat (gopt_data
.app_file
, _("-bugreport.txt"), NULL
);
1311 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog
), filename
);
1314 if (gtk_dialog_run (GTK_DIALOG (dialog
)) == GTK_RESPONSE_ACCEPT
) {
1316 GtkTextView
*text_view
;
1317 GtkTextBuffer
*buffer
;
1322 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
1323 buffer
= gtk_text_view_get_buffer (text_view
);
1324 gtk_text_buffer_get_start_iter (buffer
, &start
);
1325 gtk_text_buffer_get_end_iter (buffer
, &end
);
1326 text
= gtk_text_buffer_get_text (buffer
, &start
, &end
, FALSE
);
1328 filename
= gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog
));
1329 save_to_file (filename
, text
);
1335 gtk_widget_destroy (dialog
);
1337 bug_buddy_quit (ui
);
1342 focus_details (GtkWidget
*widget
, gpointer data
)
1344 gtk_widget_grab_focus (widget
);
1350 unknown_app_finished (GtkBuilder
*ui
)
1355 /* add the include file information now that gdb has run */
1356 if (gopt_data
.include_file
!= NULL
) {
1357 fill_include_file (gopt_data
.include_file
, gopt_data
.own_file
, ui
);
1360 fill_stderr_info (ui
);
1362 /* don't need user input, so hide these widgets */
1363 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "final-box")));
1364 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "progressbar")));
1366 /* make the send button into a save button :-) */
1367 button
= GTK_WIDGET (gtk_builder_get_object (ui
, "send-button"));
1368 gtk_button_set_label (GTK_BUTTON (button
), _("_Save Bug Report"));
1369 gtk_button_set_use_underline (GTK_BUTTON (button
), TRUE
);
1370 g_signal_connect (GTK_BUTTON (button
), "clicked", G_CALLBACK (on_save_clicked
), ui
);
1371 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui
, "send-button")));
1373 label_text
= g_markup_printf_escaped (_("The application %s has crashed.\n"
1374 "Information about the crash has been successfully collected.\n\n"
1375 "This application is not known to Bug Buddy, therefore the "
1376 "bug report cannot be sent to the GNOME Bugzilla. Please save the "
1377 "bug to a text file and report it to the appropriate bug tracker "
1378 "for this application."), gopt_data
.app_file
);
1379 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui
, "main-text")), label_text
);
1381 /* FIXME: If we just grab the focus here to the GtkTextView it will crash on the blink_cb because
1382 * the window is nop mapped! Is this a gtk+ bug? are we doing something wrong?
1383 * Let's do a funny Workaround: */
1384 g_signal_connect_after (gtk_builder_get_object (ui
, "details-view"), "realize", G_CALLBACK (focus_details
), NULL
);
1385 gtk_widget_realize (GTK_WIDGET (gtk_builder_get_object (ui
, "details-view")));
1392 gdb_finished_unknown_app (const gchar
*stacktrace
, gpointer data
)
1394 GtkBuilder
*ui
= (GtkBuilder
*) data
;
1396 gdb_insert_text (stacktrace
, ui
);
1397 unknown_app_finished (ui
);
1401 bug_buddy_quit (GtkBuilder
*ui
)
1405 g_return_if_fail (ui
!= NULL
);
1407 data
= g_object_get_data (G_OBJECT (ui
), "sourceid");
1410 guint source_id
= GPOINTER_TO_UINT (data
);
1412 /* removes the context from the main loop and kills any remaining
1414 if (source_id
> 0) {
1415 g_source_remove (source_id
);
1416 g_object_set_data (G_OBJECT (ui
), "sourceid", GUINT_TO_POINTER (0));
1420 g_hash_table_destroy (apps
);
1422 g_object_unref (ui
);
1428 keypress_callback (GtkWidget
*widget
, GdkEventKey
*event
, gpointer data
)
1430 if (event
->keyval
== GDK_Escape
) {
1431 close_callback (NULL
, data
);
1434 /* let others handle the event */
1439 close_callback (GtkWidget
*widget
, gpointer user_data
)
1441 GtkBuilder
*ui
= (GtkBuilder
*)user_data
;
1443 bug_buddy_quit (ui
);
1447 help_callback (GtkWidget
*widget
, gpointer user_data
)
1449 GError
*error
= NULL
;
1452 res
= gtk_show_uri (gtk_widget_get_screen (widget
),
1453 "ghelp:user-guide#feedback-bugs",
1454 gtk_get_current_event_time (), &error
);
1457 GtkWidget
*error_dialog
=
1458 gtk_message_dialog_new (NULL
,
1462 _("There was an error displaying help: %s"),
1465 g_signal_connect (G_OBJECT (error_dialog
), "response",
1466 G_CALLBACK (gtk_widget_destroy
), NULL
);
1468 gtk_window_set_resizable (GTK_WINDOW (error_dialog
), FALSE
);
1470 gtk_widget_show (error_dialog
);
1471 g_error_free (error
);
1477 delete_callback (GtkWidget
*widget
, GdkEvent
*event
, gpointer data
)
1479 close_callback (NULL
, data
);
1484 fill_gnome_info (BugzillaApplication
*app
, GnomeVersionInfo
*gnome_version
, GtkBuilder
*ui
)
1488 GtkTextView
*text_view
;
1490 GtkTextBuffer
*buffer
;
1492 g_return_if_fail (app
!= NULL
);
1493 g_return_if_fail (gnome_version
!= NULL
);
1494 g_return_if_fail (ui
!= NULL
);
1496 distro
= get_distro_name ();
1497 version_info
= g_strdup_printf ("Distribution: %s\n"
1498 "Gnome Release: %s %s (%s)\n"
1499 "BugBuddy Version: %s\n"
1502 gnome_version
->gnome_platform
, gnome_version
->gnome_date
,
1503 gnome_version
->gnome_distributor
, VERSION
);
1507 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
1508 buffer
= gtk_text_view_get_buffer (text_view
);
1509 gtk_text_buffer_get_end_iter (buffer
, &end
);
1510 gtk_text_buffer_insert (buffer
, &end
, version_info
, strlen (version_info
));
1512 g_free (version_info
);
1516 fill_custom_info (BugzillaApplication
*app
, GtkBuilder
*ui
)
1518 GtkTextView
*text_view
;
1520 GtkTextBuffer
*buffer
;
1523 gchar
*standard_output
= NULL
;
1524 gchar
*standard_error
= NULL
;
1525 GError
*error
= NULL
;
1527 g_return_if_fail (app
!= NULL
);
1528 g_return_if_fail (ui
!= NULL
);
1530 /* fill the include info now that gdb has run */
1531 if (gopt_data
.include_file
!= NULL
) {
1532 fill_include_file (gopt_data
.include_file
, gopt_data
.own_file
, ui
);
1535 if (app
->extra_info_script
== NULL
) {
1539 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
1540 buffer
= gtk_text_view_get_buffer (text_view
);
1541 gtk_text_buffer_get_end_iter (buffer
, &end
);
1543 if (!g_spawn_command_line_sync (app
->extra_info_script
, &standard_output
, &standard_error
,
1545 gchar
*error_string
= g_strdup_printf ("There was an error running \"%s\" script:\n"
1546 "%s", app
->extra_info_script
, error
->message
);
1547 gtk_text_buffer_insert (buffer
, &end
, error_string
, strlen (error_string
));
1550 g_free (error_string
);
1554 output
= g_strdup_printf ("Output of custom script \"%s\":\n"
1556 app
->extra_info_script
,
1557 standard_output
? standard_output
: "");
1559 gtk_text_buffer_insert (buffer
, &end
, output
, strlen (output
));
1562 g_free (standard_output
);
1563 g_free (standard_error
);
1568 fill_proccess_info (pid_t pid
, GtkBuilder
*ui
)
1570 GtkTextView
*text_view
;
1572 GtkTextBuffer
*buffer
;
1575 char *proccess_info
;
1577 mem
= proccess_get_mem_state (pid
);
1578 time
= proccess_get_time (pid
);
1580 proccess_info
= g_strdup_printf ("%s\n"
1588 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
1589 buffer
= gtk_text_view_get_buffer (text_view
);
1590 gtk_text_buffer_get_end_iter (buffer
, &end
);
1591 gtk_text_buffer_insert (buffer
, &end
, proccess_info
, strlen (proccess_info
));
1593 g_free (proccess_info
);
1598 fill_include_file (char *filename
, gboolean own_file
, GtkBuilder
*ui
)
1600 GtkTextView
*text_view
;
1602 GtkTextBuffer
*buffer
;
1605 GError
*error
= NULL
;
1607 res
= g_file_get_contents (filename
, &text
, NULL
, &error
);
1609 /* if own_file is set, delete the file regardless of the result:
1610 * if _get_contents () fails chances are the file has already been
1615 if (g_unlink (filename
) == -1)
1616 g_warning ("Unable to delete %s", filename
);
1620 buddy_error (NULL
, error
->message
);
1621 g_error_free (error
);
1625 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
1626 buffer
= gtk_text_view_get_buffer (text_view
);
1627 gtk_text_buffer_get_end_iter (buffer
, &end
);
1628 gtk_text_buffer_insert (buffer
, &end
, text
, strlen (text
));
1634 /* copied from gconf-editor/gconf-util.c */
1637 gconf_get_key_name_from_path (const gchar
*path
)
1641 ptr
= path
+ strlen (path
);
1643 while (ptr
[-1] != '/')
1646 return g_strdup (ptr
);
1650 fill_system_info (GtkBuilder
*ui
)
1652 GConfClient
*gconf_client
;
1653 GtkTextView
*text_view
;
1655 GtkTextBuffer
*buffer
;
1656 GString
*system_info
, *modules
;
1658 struct utsname uts_buf
;
1660 gboolean has_selinux
, enforcing
, a11y
;
1662 g_return_if_fail (ui
!= NULL
);
1664 system_info
= g_string_new ("");
1667 if (uname (&uts_buf
) == 0) {
1668 g_string_append_printf (system_info
, "System: %s %s %s %s\n", uts_buf
.sysname
, uts_buf
.release
, uts_buf
.version
, uts_buf
.machine
);
1671 /* X server checks */
1672 g_string_append_printf (system_info
, "X Vendor: %s\n", ServerVendor (gdk_display_get_default()));
1673 g_string_append_printf (system_info
, "X Vendor Release: %d\n", VendorRelease (gdk_display_get_default()));
1676 /* Selinux checks */
1677 has_selinux
= FALSE
;
1678 if (g_file_get_contents ("/proc/filesystems", &str
, NULL
, NULL
)) {
1679 has_selinux
= strstr (str
, "selinuxfs") != NULL
;
1684 if (g_file_get_contents ("/selinux/enforce", &str
, NULL
, NULL
)) {
1685 enforcing
= strcmp (str
, "0") != 0;
1688 g_string_append_printf (system_info
, "Selinux: %s\n", enforcing
?"Enforcing":"Permissive");
1690 g_string_append_printf (system_info
, "Selinux: No\n");
1694 gconf_client
= gconf_client_get_default ();
1695 a11y
= gconf_client_get_bool (gconf_client
, ACCESSIBILITY_KEY
, NULL
);
1696 g_string_append_printf (system_info
, "Accessibility: %s\n", a11y
?"Enabled":"Disabled");
1697 str
= gconf_client_get_string (gconf_client
, GTK_THEME_KEY
, NULL
);
1698 g_string_append_printf (system_info
, "GTK+ Theme: %s\n", str
);
1700 str
= gconf_client_get_string (gconf_client
, ICON_THEME_KEY
, NULL
);
1701 g_string_append_printf (system_info
, "Icon Theme: %s\n", str
);
1704 /* add the GTK+ loaded modules. to do that, we look both in GConf and
1705 * inside the GTK_MODULES env var.
1708 entries
= gconf_client_all_entries (gconf_client
, GTK_MODULES_KEY
, NULL
);
1715 modules
= g_string_new ("GTK+ Modules: ");
1717 for (l
= entries
; l
!= NULL
; l
= l
->next
) {
1719 val
= gconf_entry_get_value (entry
);
1721 /* if the value is a boolean, check if it's activated, otherwise if it's
1722 * a string, it will refer to a boolean; check that.
1723 * i know this is quite ugly.
1725 if ((val
->type
== GCONF_VALUE_BOOL
&& gconf_value_get_bool (val
)) ||
1726 (val
->type
== GCONF_VALUE_STRING
&& gconf_client_get_bool (gconf_client
,
1727 gconf_value_get_string (val
),
1731 name
= gconf_get_key_name_from_path (gconf_entry_get_key (entry
));
1733 if (!g_strstr_len (modules
->str
, modules
->len
, name
)) {
1734 g_string_append_printf (modules
, "%s, ", name
);
1740 gconf_entry_unref (entry
);
1743 g_slist_free (entries
);
1746 str
= (char * ) g_getenv ("GTK_MODULES");
1752 modules
= g_string_new ("GTK+ Modules: ");
1755 /* modules are divided by G_SEARCHPATH_SEPARATOR */
1756 splitted
= pango_split_file_list (str
);
1758 for (i
= 0; splitted
[i
]; i
++) {
1759 if (!g_strstr_len (modules
->str
, modules
->len
, splitted
[i
])) {
1760 g_string_append_printf (modules
, "%s, ", splitted
[i
]);
1764 g_strfreev (splitted
);
1768 /* discard the last ", " */
1769 g_string_append_len (system_info
, modules
->str
, (modules
->len
- 2));
1770 g_string_append (system_info
, "\n");
1771 g_string_free (modules
, TRUE
);
1774 g_object_unref (gconf_client
);
1776 g_string_append (system_info
, "\n");
1778 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
1779 buffer
= gtk_text_view_get_buffer (text_view
);
1780 gtk_text_buffer_get_end_iter (buffer
, &end
);
1781 gtk_text_buffer_insert (buffer
, &end
, system_info
->str
, system_info
->len
);
1783 g_string_free (system_info
, TRUE
);
1787 fill_stderr_info (GtkBuilder
*ui
)
1789 GtkTextView
*text_view
;
1791 GtkTextBuffer
*buffer
;
1792 GString
*stderr_info
;
1797 g_return_if_fail (ui
!= NULL
);
1799 stderr_info
= g_string_new ("");
1801 /* .xsession-errors: read file */
1802 file
= g_build_filename (g_get_home_dir (), ".xsession-errors", NULL
);
1803 if (g_file_get_contents (file
, &str
, NULL
, NULL
)) {
1804 lines
= g_strsplit (str
, "\n", -1);
1807 while (lines
[n_lines
] != NULL
) {
1813 char *mtime_age
= NULL
;
1817 if (stat (file
, &buf
) == 0) {
1818 age
= time (NULL
) - buf
.st_mtime
;
1820 mtime_age
= g_strdup_printf (" (%d sec old)", (int) age
);
1824 g_string_append_printf (stderr_info
,
1825 "\n\n----------- .xsession-errors%s ---------------------\n",
1826 mtime_age
?mtime_age
:"");
1830 for (i
= MAX (0, n_lines
-16); i
< n_lines
; i
++) {
1831 if (lines
[i
][0] != 0) {
1832 /* Limit line length to 200 chars to avoid excessive data */
1833 if (strlen (lines
[i
]) > 200) {
1837 g_string_append_printf (stderr_info
, "%s\n", lines
[i
]);
1841 g_string_append (stderr_info
, "--------------------------------------------------\n");
1847 text_view
= GTK_TEXT_VIEW (gtk_builder_get_object (ui
, "gdb-text"));
1848 buffer
= gtk_text_view_get_buffer (text_view
);
1849 gtk_text_buffer_get_end_iter (buffer
, &end
);
1850 gtk_text_buffer_insert (buffer
, &end
, stderr_info
->str
, stderr_info
->len
);
1852 g_string_free (stderr_info
, TRUE
);
1859 gchar
*gdb
= g_find_program_in_path ("gdb");
1869 is_gnome_hacker (void)
1871 if (g_getenv ("GNOME_HACKER"))
1878 run_gdb (const gchar
*appname
, pid_t pid
)
1883 GError
*error
= NULL
;
1885 title
= g_strdup_printf ("Debugging %s", appname
);
1887 exec_str
= g_strdup_printf ("gnome-terminal "
1889 "--disable-factory "
1890 "--command=\"gdb %s %d\"",
1891 title
, appname
, (int) pid
);
1893 res
= g_spawn_command_line_sync (exec_str
, NULL
, NULL
,
1897 g_warning ("Couldn't run debugger\n");
1905 main (int argc
, char *argv
[])
1908 BugzillaApplication
*app
;
1909 GnomeVersionInfo
*gnome_version
;
1911 GtkWidget
*main_window
;
1912 GOptionContext
*context
;
1915 GtkBuilder
*ui
= NULL
;
1917 memset (&gopt_data
, 0, sizeof (gopt_data
));
1919 bindtextdomain (GETTEXT_PACKAGE
, GNOMELOCALEDIR
);
1920 bind_textdomain_codeset (GETTEXT_PACKAGE
, "UTF-8");
1921 textdomain (GETTEXT_PACKAGE
);
1923 context
= g_option_context_new (N_("\n\nBug Buddy is a utility that helps report debugging\n"
1924 "information to the GNOME Bugzilla when a program crashes."));
1926 g_option_context_set_translation_domain (context
, GETTEXT_PACKAGE
);
1927 g_option_context_add_main_entries (context
, options
, GETTEXT_PACKAGE
);
1928 g_option_context_add_group (context
, gtk_get_option_group (TRUE
));
1930 if (!g_option_context_parse (context
, &argc
, &argv
, &err
)) {
1931 g_critical ("Failed to parse arguments: %s\n", err
->message
);
1933 g_option_context_free (context
);
1937 g_option_context_free (context
);
1939 if (!bonobo_activation_is_initialized ())
1940 bonobo_activation_init (argc
, argv
);
1942 g_set_application_name (_("Bug Buddy"));
1943 gtk_window_set_default_icon_name ("bug-buddy");
1945 if (gopt_data
.app_file
&& gopt_data
.pid
) {
1946 if (has_gdb () && is_gnome_hacker ()) {
1948 res
= run_gdb (gopt_data
.app_file
, gopt_data
.pid
);
1954 /* FIXME: Request PackageKit to install gdb */
1958 if (!elf_has_debug_symbols (gopt_data
.pid
)) {
1959 /* FIXME: Request PackageKit to install debug symbols packages */
1965 s
= g_build_filename (BUDDY_DATADIR
, "bug-buddy.gtkbuilder", NULL
);
1967 ui
= gtk_builder_new ();
1968 gtk_builder_add_from_file (ui
, s
, &err
);
1969 gtk_builder_set_translation_domain (ui
, GETTEXT_PACKAGE
);
1975 _("Bug Buddy could not load its user interface file.\n"
1976 "Please make sure Bug Buddy was installed correctly."));
1983 main_window
= GTK_WIDGET (gtk_builder_get_object (ui
, "main-window"));
1984 g_signal_connect (main_window
, "delete-event", G_CALLBACK (delete_callback
), ui
);
1985 g_signal_connect (main_window
, "key-press-event", G_CALLBACK (keypress_callback
), ui
);
1987 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "final-box")));
1989 progress
= g_timeout_add (100, update_progress_bar
,
1990 gtk_builder_get_object (ui
, "progressbar"));
1991 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui
, "progressbar")),
1992 _("Collecting information from your system…"));
1994 if (gopt_data
.app_file
== NULL
&& gopt_data
.package
== NULL
) {
1995 buddy_error (NULL
, _("Either --appname or --package arguments are required.\n"));
1999 if (gopt_data
.app_file
&& gopt_data
.pid
== 0 &&
2000 gopt_data
.include_file
== NULL
) {
2001 buddy_error (NULL
, _("Either --pid or --include arguments are required.\n"));
2005 if (gopt_data
.own_file
== TRUE
&& gopt_data
.include_file
== NULL
) {
2006 buddy_error (NULL
, _("The --unlink-tempfile option needs an --include argument.\n"));
2010 /* get some information about the gnome version */
2011 gnome_version
= get_gnome_version_info ();
2012 if (gnome_version
== NULL
) {
2013 buddy_error (NULL
, _("Bug Buddy was unable to retrieve information regarding "
2014 "the version of GNOME you are running. This is most likely "
2015 "due to a missing installation of gnome-desktop.\n"));
2019 g_object_set_data (G_OBJECT (ui
), "gnome-version", gnome_version
);
2021 /* connect the signal handler for the help button */
2022 g_signal_connect (gtk_builder_get_object (ui
, "help-button"), "clicked",
2023 G_CALLBACK (help_callback
), NULL
);
2025 gtk_widget_show (main_window
);
2027 apps
= load_applications ();
2029 /* If we have a binary file it is a crash */
2030 if (gopt_data
.app_file
) {
2031 app
= g_hash_table_lookup (apps
, gopt_data
.app_file
);
2033 /* we handle an unknown application (no .desktop file) differently */
2035 s
= g_markup_printf_escaped (_("The %s application has crashed. "
2036 "We are collecting information about the crash to send to the "
2037 "developers in order to fix the problem."), app
->name
);
2038 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui
, "main-text")), s
);
2041 g_object_set_data (G_OBJECT (ui
), "app", app
);
2044 gtk_image_set_from_icon_name (GTK_IMAGE (gtk_builder_get_object (ui
, "app-image")),
2046 GTK_ICON_SIZE_DIALOG
);
2048 fill_gnome_info (app
, gnome_version
, ui
);
2051 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui
, "progressbar")),
2052 _("Collecting information from the crash…"));
2054 fill_system_info (ui
);
2056 fill_proccess_info (gopt_data
.pid
, ui
);
2058 if (gopt_data
.pid
> 0) {
2059 /* again, if this is an unknown application, we connect a different callback that
2060 * will allow the user to save the trace rather than sending it to the GNOME Bugzilla */
2062 source_id
= gdb_get_trace (gopt_data
.app_file
, gopt_data
.pid
, ui
,
2063 gdb_finished_unknown_app
, &err
);
2065 source_id
= gdb_get_trace (gopt_data
.app_file
, gopt_data
.pid
, ui
, gdb_finished
, &err
);
2068 if (source_id
== 0) {
2069 buddy_error (NULL
, _("Bug Buddy encountered the following error when trying "
2070 "to retrieve debugging information: %s\n"), err
->message
);
2075 /* connect the close button callback so that we can remove the source from
2076 * the main loop (and kill gdb) if the user wants to quit before gdb is finished */
2077 g_object_set_data (G_OBJECT (ui
), "sourceid", GUINT_TO_POINTER (source_id
));
2080 unknown_app_finished (ui
);
2082 known_app_finished (ui
);
2085 g_object_set_data (G_OBJECT (ui
), "type", GINT_TO_POINTER(BUG_TYPE_CRASH
));
2087 /* No binary file, so this is a non-crashing bug. Look the application from the --package arg */
2088 GtkWidget
*email_entry
;
2089 char *default_email
;
2090 char *msg
, *lang_note
;
2091 const char *en_lang_note
= N_("\n\nPlease write your report in English, if possible.");
2093 app
= g_hash_table_find (apps
, (GHRFunc
)bugzilla_search_for_package
, gopt_data
.package
);
2095 /* Fallback to binary name */
2096 app
= g_hash_table_lookup (apps
, gopt_data
.package
);
2099 buddy_error (NULL
, _("Bug Buddy doesn't know how to send a suggestion for the application %s.\n"),
2104 g_object_set_data (G_OBJECT (ui
), "app", app
);
2107 gtk_image_set_from_icon_name (GTK_IMAGE (gtk_builder_get_object (ui
, "app-image")),
2109 GTK_ICON_SIZE_DIALOG
);
2111 fill_gnome_info (app
, gnome_version
, ui
);
2112 fill_custom_info (app
, ui
);
2114 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui
, "final-box")));
2115 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui
, "send-button")));
2116 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui
, "progressbar")));
2118 lang_note
= gettext (en_lang_note
);
2119 msg
= g_strconcat (_("Thank you for helping us to improve our software.\n"
2120 "Please fill in your suggestions/error information for %s application.\n\n"
2121 "A valid email address is required. This will allow developers to "
2122 "contact you for more information if necessary."),
2123 strcmp (lang_note
, en_lang_note
)? lang_note
: NULL
,
2125 s
= g_markup_printf_escaped (msg
, app
->name
);
2126 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui
, "main-text")), s
);
2130 s
= g_markup_printf_escaped ("<span weight=\"bold\">%s</span>",
2131 _("Suggestion / Error description:"));
2132 gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui
, "main-label")), s
);
2135 show_pending_checkbox_if_pending (ui
);
2136 g_signal_connect (gtk_builder_get_object (ui
, "send-button"), "clicked",
2137 G_CALLBACK (on_send_clicked
), ui
);
2139 email_entry
= GTK_WIDGET (gtk_builder_get_object (ui
, "email-entry"));
2140 g_signal_connect (email_entry
, "changed", G_CALLBACK (check_email
), ui
);
2142 default_email
= get_default_user_email ();
2144 if (default_email
!= NULL
) {
2145 gtk_entry_set_text (GTK_ENTRY (email_entry
), default_email
);
2146 g_free (default_email
);
2148 g_object_set_data (G_OBJECT (ui
), "type", GINT_TO_POINTER (BUG_TYPE_REQUEST
));
2151 g_signal_connect (gtk_builder_get_object (ui
, "close-button"), "clicked",
2152 G_CALLBACK (close_callback
), ui
);