Actually hide the "Send" button after clicking it (#556129).
[bug-buddy.git] / src / bug-buddy.c
blob062ad8aa2c6194ca334ea2274bde9e91e252cfb8
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.
22 #include "config.h"
24 #include "eds-buddy.h"
25 #include "gdb-buddy.h"
26 #include "bugzilla.h"
27 #include "bug-buddy.h"
28 #include "distribution.h"
29 #include "proccess.h"
30 #include "forbidden-words.h"
32 #include <stdio.h>
33 #include <unistd.h>
34 #include <string.h>
35 #include <sys/utsname.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
39 #include <errno.h>
41 #include <glib.h>
42 #include <glib/gstdio.h>
43 #include <glib/gi18n.h>
44 #include <gio/gio.h>
45 #include <gnome.h>
46 #include <gtk/gtk.h>
47 #include <gdk-pixbuf/gdk-pixbuf.h>
48 #include <libgnome/libgnometypebuiltins.h>
49 #include <gdk/gdkx.h>
50 #include <gdk/gdk.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 DESKTOP_IS_HOME_DIR "/apps/nautilus/preferences/desktop_is_home_dir"
69 #define MIN_REPORT_DETAILS_CHARS 10
71 static GOptionData gopt_data;
72 static int bug_count = 0;
73 static GHashTable *apps = NULL;
75 static const GOptionEntry options[] = {
76 { "package", '\0', 0, G_OPTION_ARG_STRING, &gopt_data.package, N_("Package containing the program"), N_("PACKAGE") },
77 { "appname", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.app_file, N_("File name of crashed program"), N_("FILE") },
78 { "pid", '\0', 0, G_OPTION_ARG_INT, &gopt_data.pid, N_("PID of crashed program"), N_("PID") },
79 { "include", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.include_file, N_("Text file to include in the report"), N_("FILE") },
80 { "minidump", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.minidump_file, N_("MiniDump file with info about the crash"), N_("FILE") },
81 { NULL }
84 static void fill_stderr_info (GtkBuilder *ui);
85 static void fill_custom_info (BugzillaApplication *app, GtkBuilder *ui);
86 static void close_callback (GtkWidget *widget, gpointer user_data);
87 static void bug_buddy_quit (GtkBuilder *ui);
90 static void
91 buddy_error (GtkWidget *parent, const char *msg, ...)
93 GtkWidget *w;
94 GtkDialog *d;
95 gchar *s;
96 va_list args;
98 /* No va_list version of dialog_new, construct the string ourselves. */
99 va_start (args, msg);
100 s = g_strdup_vprintf (msg, args);
101 va_end (args);
103 w = gtk_message_dialog_new (GTK_WINDOW (parent),
105 GTK_MESSAGE_ERROR,
106 GTK_BUTTONS_OK,
107 "%s",
109 d = GTK_DIALOG (w);
110 gtk_dialog_set_default_response (d, GTK_RESPONSE_OK);
111 gtk_dialog_run (d);
112 gtk_widget_destroy (w);
113 g_free (s);
116 static void
117 lock_text (GtkBuilder *ui)
119 GtkTextView *text_view;
120 GtkTextBuffer *buffer;
121 GtkTextIter start;
122 GtkTextIter end;
123 static GtkTextTag *tag = NULL;
124 char *text;
126 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
127 buffer = gtk_text_view_get_buffer (text_view);
128 gtk_text_buffer_get_start_iter (buffer, &start);
129 gtk_text_buffer_get_end_iter (buffer, &end);
130 text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
132 if (!tag) {
133 GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (text_view));
134 tag = gtk_text_buffer_create_tag (buffer, "lock_tag",
135 "editable", FALSE,
136 /* I don't like how it looks like dimming also fg
137 "foreground-gdk", &style->fg[GTK_STATE_INSENSITIVE], */
138 "background-gdk", &style->bg[GTK_STATE_INSENSITIVE],
139 NULL);
142 if (gtk_text_iter_forward_search (&start, "Backtrace was generated from",
143 GTK_TEXT_SEARCH_TEXT_ONLY,
144 NULL, &end, NULL)) {
145 gtk_text_iter_forward_line (&end);
146 gtk_text_buffer_apply_tag_by_name (buffer, "lock_tag", &start, &end);
151 static gboolean
152 search_forbidden_words (GtkBuilder *ui)
154 GtkTextView *text_view;
155 GtkTextBuffer *buffer;
156 int i;
157 gboolean found = FALSE;
158 static GtkTextTag *tag = NULL;
160 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
161 buffer = gtk_text_view_get_buffer (text_view);
162 if (!tag) {
163 tag = gtk_text_buffer_create_tag (buffer, "forbbiden_tag",
164 "foreground", "white",
165 "background", "blue",
166 NULL);
169 for (i = 0; forbidden_words[i]; i++) {
170 GtkTextIter start;
171 GtkTextIter end;
173 gtk_text_buffer_get_start_iter (buffer, &start);
174 while (gtk_text_iter_forward_search (&start, forbidden_words[i],
175 GTK_TEXT_SEARCH_TEXT_ONLY,
176 &start, &end, NULL)) {
177 gtk_text_buffer_apply_tag_by_name (buffer, "forbbiden_tag", &start, &end);
178 start = end;
179 found = TRUE;
183 return found;
188 static void
189 copy_review (GtkWidget *button, gpointer data)
191 GtkTextView *text_view;
192 GtkTextBuffer *buffer;
193 GtkTextIter start;
194 GtkTextIter end;
195 GtkBuilder *ui = (GtkBuilder*) data;
197 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
198 buffer = gtk_text_view_get_buffer (text_view);
199 gtk_text_buffer_get_start_iter (buffer, &start);
200 gtk_text_buffer_get_end_iter (buffer, &end);
201 gtk_text_buffer_select_range (buffer, &start, &end);
202 gtk_text_buffer_copy_clipboard (buffer, gtk_clipboard_get (GDK_NONE));
206 static void
207 edit_review (GtkWidget *button, gpointer data)
209 GtkTextView *text_view;
210 GtkBuilder *ui = (GtkBuilder*) data;
211 gboolean editable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
213 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
214 gtk_text_view_set_editable (text_view, editable);
215 if (editable) {
216 lock_text (ui);
221 static void
222 close_review (GtkWidget *button, gpointer data)
224 GtkWidget *review_dialog = GTK_WIDGET (data);
226 gtk_widget_hide (review_dialog);
229 static gboolean
230 delete_review (GtkWidget *widget, GdkEvent *event, gpointer user_data)
232 gtk_widget_hide (widget);
234 return TRUE; /* don't destroy */
237 static void
238 show_review (GtkWidget *button, gpointer data)
240 GtkWidget *review_dialog, *main_window;
241 GtkWidget *edit, *copy, *close;
242 GtkBuilder *ui = (GtkBuilder*) data;
243 static gboolean initialized = FALSE;
245 if (!initialized) {
246 review_dialog = GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog"));
247 main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window"));
248 copy = GTK_WIDGET (gtk_builder_get_object (ui, "copy-review-button"));
249 edit = GTK_WIDGET (gtk_builder_get_object (ui, "edit-review-button"));
250 close = GTK_WIDGET (gtk_builder_get_object (ui, "close-review-button"));
252 gtk_window_set_transient_for (GTK_WINDOW (review_dialog), GTK_WINDOW (main_window));
254 g_signal_connect (G_OBJECT (copy), "clicked", G_CALLBACK (copy_review), ui);
255 g_signal_connect (G_OBJECT (edit), "toggled", G_CALLBACK (edit_review), ui);
256 g_signal_connect (G_OBJECT (close), "clicked", G_CALLBACK (close_review), review_dialog);
257 g_signal_connect (G_OBJECT (review_dialog), "delete-event", G_CALLBACK (delete_review), NULL);
259 initialized = TRUE;
262 lock_text (ui);
264 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog")));
268 static GnomeVersionInfo*
269 get_gnome_version_info (void)
271 GnomeVersionInfo *version;
272 xmlDoc *doc;
273 char *xml_file;
274 xmlNode *node;
275 guchar *platform, *minor, *micro, *distributor, *date;
277 version = g_new0 (GnomeVersionInfo, 1);
279 xml_file = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_DATADIR,
280 "gnome-about/gnome-version.xml",
281 TRUE, NULL);
282 if (!xml_file)
283 return NULL;
284 doc = xmlParseFile (xml_file);
285 g_free (xml_file);
287 if (!doc)
288 return NULL;
290 platform = minor = micro = distributor = date = NULL;
292 for (node = xmlDocGetRootElement (doc)->children; node; node = node->next) {
293 if (!strcmp ((char *)node->name, "platform"))
294 platform = xmlNodeGetContent (node);
295 else if (!strcmp ((char *)node->name, "minor"))
296 minor = xmlNodeGetContent (node);
297 else if (!strcmp ((char *)node->name, "micro"))
298 micro = xmlNodeGetContent (node);
299 else if (!strcmp ((char *)node->name, "distributor"))
300 distributor = xmlNodeGetContent (node);
301 else if (!strcmp ((char *)node->name, "date"))
302 date = xmlNodeGetContent (node);
305 if (platform && minor && micro)
306 version->gnome_platform = g_strdup_printf ("%s.%s.%s", platform, minor, micro);
308 if (distributor && *distributor)
309 version->gnome_distributor = g_strdup ((char *)distributor);
311 if (date && *date)
312 version->gnome_date = g_strdup ((char *)date);
314 xmlFree (platform);
315 xmlFree (minor);
316 xmlFree (micro);
317 xmlFree (distributor);
318 xmlFree (date);
320 xmlFreeDoc (doc);
322 return version;
325 static gboolean
326 update_progress_bar (gpointer data)
328 GtkProgressBar *pbar = GTK_PROGRESS_BAR (data);
330 gtk_progress_bar_pulse (pbar);
332 return TRUE;
335 static void
336 save_email (const char *email)
338 GConfClient *conf_client;
340 conf_client = gconf_client_get_default ();
341 gconf_client_set_string (conf_client, "/apps/bug-buddy/email_address", email, NULL);
342 g_object_unref (conf_client);
345 static void
346 copy_link_item_activated_cb (GtkMenuItem *menu_item,
347 const gchar *link)
349 GtkClipboard *clipboard;
351 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
352 gtk_clipboard_set_text (clipboard, link, -1);
355 static GtkWidget *
356 build_link_menu (const gchar *link)
358 GtkWidget *menu;
359 GtkWidget *item;
360 GtkWidget *image;
362 menu = gtk_menu_new ();
363 item = gtk_image_menu_item_new_with_mnemonic (_("Copy _Link Address"));
364 image = gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
365 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
366 gtk_widget_show_all (item);
367 g_signal_connect (item, "activate",
368 G_CALLBACK (copy_link_item_activated_cb), (char *) link);
369 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
371 return menu;
374 static gboolean
375 link_button_press_event_cb (GtkWidget *widget,
376 GdkEventButton *event,
377 gpointer data)
379 const gchar *link = gtk_link_button_get_uri (GTK_LINK_BUTTON (widget));
381 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
382 GtkWidget *popup;
384 popup = build_link_menu (link);
385 gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL,
386 event->button, event->time);
388 return TRUE;
391 return FALSE;
394 static void
395 link_callback (GtkLinkButton *button, gpointer user_data)
397 const gchar *link = gtk_link_button_get_uri (button);
398 #if GTK_CHECK_VERSION (2,13,0)
399 GdkAppLaunchContext *context;
401 context = gdk_app_launch_context_new ();
402 gdk_app_launch_context_set_screen (context,
403 gtk_widget_get_screen (GTK_WIDGET (button)));
404 gdk_app_launch_context_set_timestamp (context,
405 gtk_get_current_event_time ());
407 if (!g_app_info_launch_default_for_uri (link,
408 G_APP_LAUNCH_CONTEXT (context),
409 NULL))
410 #else
411 if (!g_app_info_launch_default_for_uri (link, NULL, NULL))
412 #endif
414 char *text;
416 text = g_markup_printf_escaped (_("Bug Buddy was unable to view the link \"%s\"\n"), link);
417 buddy_error (NULL, text);
418 g_free (text);
421 #if GTK_CHECK_VERSION (2,13,0)
422 g_object_unref (context);
423 #endif
425 return;
428 static void
429 save_to_file (const gchar *filename, const gchar *text)
431 GError *error = NULL;
433 if (!g_file_set_contents (filename, text, -1, &error)) {
434 g_warning ("Unable to save document %s: %s\n", filename, error->message);
435 g_error_free (error);
440 static void
441 network_error (SoupMessage *msg, GtkBuilder *ui)
444 GtkWidget *dialog;
445 int res;
447 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
449 dialog = gtk_message_dialog_new_with_markup (NULL,
450 GTK_DIALOG_MODAL,
451 GTK_MESSAGE_WARNING,
452 GTK_BUTTONS_YES_NO,
453 _("There has been a network error while sending the report. "
454 "Do you want to save this report and send it later?"));
455 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
456 "%s", _("Please ensure that your Internet connection is active "
457 "and working correctly."));
458 res = gtk_dialog_run (GTK_DIALOG (dialog));
459 gtk_widget_destroy (dialog);
461 if (res == GTK_RESPONSE_YES) {
462 gchar *dirname;
463 gchar *filename;
465 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
466 if (!g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
467 g_mkdir_with_parents (dirname, 0755);
470 filename = g_strdup_printf ("%s/%ld", dirname, (long)time (NULL));
472 save_to_file (filename, msg->request_body->data);
474 g_free (dirname);
475 g_free (filename);
478 bug_buddy_quit (ui);
479 return;
482 static void
483 remove_pending_reports (void)
485 GDir *dir;
486 char *dirname;
487 GError *error = NULL;
489 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
490 dir = g_dir_open (dirname, 0, &error);
491 if (dir) {
492 const char *name = g_dir_read_name (dir);
493 while (name) {
494 char *path = g_strdup_printf ("%s/%s", dirname, name);
495 g_remove (path);
496 g_free (path);
497 name = g_dir_read_name (dir);
499 g_dir_close (dir);
502 g_remove (dirname);
503 g_free (dirname);
506 static void
507 all_sent (GtkBuilder *ui)
509 GtkWidget *close_button;
511 /* hide the progressbar */
512 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
514 close_button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
515 gtk_widget_show (close_button);
518 static void
519 previous_sent (SoupSession *session, SoupMessage *msg, GtkBuilder *ui)
521 if (--bug_count == 0) {
522 all_sent (ui);
527 static void
528 bug_sent (SoupSession *session, SoupMessage *msg, GtkBuilder *ui)
530 GtkWidget *button;
531 GtkWidget *image;
532 char *text = NULL;
533 char *errmsg = NULL;
534 char *str = NULL;
535 long bugid;
536 char *response;
537 char *tmp;
538 GtkWidget *urlbutton;
539 GtkRequisition requisition;
540 GError *err = NULL;
542 button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
543 gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
544 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
546 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON),
547 gtk_button_set_image (GTK_BUTTON (button), image);
549 if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
550 network_error (msg, ui);
551 } else {
552 remove_pending_reports ();
555 /* parse the XML-RPC response */
556 response = bugzilla_parse_response (msg, &err);
557 if (response != NULL) {
558 bugid = strtol (response, &tmp, 10);
559 GtkWidget *main_vbox;
561 /* we need a reference to the vbox containing the text so that we
562 * can add a GtkLinkButton to the bug report */
563 main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox"));
565 if (response == tmp) {
566 char *url;
567 url = strstr (response, "ViewURL=");
568 if (url)
569 url += strlen ("ViewURL=");
570 else
571 url = "";
573 text = g_strdup (url);
574 } else
575 text = g_strdup_printf ("http://bugzilla.gnome.org/show_bug.cgi?id=%ld", bugid);
577 /* create a clickable link to the bug report */
578 urlbutton = gtk_link_button_new (text);
579 g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL);
580 g_signal_connect (G_OBJECT (urlbutton), "button-press-event",
581 G_CALLBACK (link_button_press_event_cb), NULL);
582 gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0);
584 gtk_widget_show (urlbutton);
585 g_free (text);
587 text = g_markup_printf_escaped (_("A bug report detailing your software crash has been sent to GNOME. "
588 "This information will allow the developers to understand the cause "
589 "of the crash and prepare a solution for it.\n\n"
590 "You may be contacted by a GNOME developer if more details are "
591 "required about the crash.\n\n"
592 "You can view your bug report and follow its progress with this URL:\n")) ;
594 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), text);
595 g_free (text);
596 save_email (gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry"))));
597 } else {
598 errmsg = _("Bug Buddy has encountered an error while submitting your report "
599 "to the Bugzilla server. Details of the error are included below.\n\n");
601 if (err && err->domain == SOUP_XMLRPC_FAULT) {
602 /* see http://cvs.gnome.org/viewcvs/bugzilla-newer/Bugzilla/RPC.pm?view=markup */
603 if (err->message == NULL) {
604 text = g_strdup_printf (_("Bugzilla reported an error when trying to process your "
605 "request, but was unable to parse the response."));
606 } else if (g_str_equal (err->message, "invalid_username")) {
607 text = g_strdup_printf (_("The email address you provided is not valid."));
608 } else if (g_str_equal (err->message, "account_disabled")) {
609 text = g_strdup_printf (_("The account associated with the email address "
610 "provided has been disabled."));
611 } else if (g_str_equal (err->message, "product_doesnt_exist")) {
612 text = g_strdup_printf (_("The product specified doesn't exist or has been "
613 "renamed. Please upgrade to the latest version."));
614 } else if (g_str_equal (err->message, "component_not_valid")) {
615 text = g_strdup_printf (_("The component specified doesn't exist or has been "
616 "renamed. Please upgrade to the latest version."));
617 } else if (g_str_equal (err->message, "require_summary")) {
618 text = g_strdup_printf (_("The summary is required in your bug report. "
619 "This should not happen with the latest Bug Buddy."));
620 } else if (g_str_equal (err->message, "description_required")) {
621 text = g_strdup_printf (_("The description is required in your bug report. "
622 "This should not happen with the latest Bug Buddy."));
623 } else {
624 text = g_strdup_printf (_("The fault code returned by Bugzilla is not recognized. "
625 "Please report the following information to "
626 "bugzilla.gnome.org manually:\n\n%s"), err->message);
628 } else if (err) {
629 switch (err->code) {
630 case BUGZILLA_ERROR_RECV_BAD_STATUS:
631 text = g_strdup_printf (_("Server returned bad state. This is most likely a server "
632 "issue and should be reported to bugmaster@gnome.org\n\n%s"),
633 err->message);
634 break;
635 case BUGZILLA_ERROR_RECV_PARSE_FAILED:
636 text = g_strdup_printf (_("Failed to parse the xml-rpc response. Response follows:\n\n%s"),
637 err->message);
638 break;
639 default:
640 text = g_strdup_printf (_("An unknown error occurred. This is most likely a problem with "
641 "bug-buddy. Please report this problem manually at bugzilla."
642 "gnome.org\n\n"));
643 break;
646 str = g_strconcat (errmsg, text, NULL);
647 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), str);
649 g_free (str);
650 g_free (text);
651 g_error_free (err);
655 if (--bug_count == 0) {
656 all_sent (ui);
659 gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui, "main-window")), &requisition);
660 gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
661 requisition.width, requisition.height);
663 g_free (response);
668 static void
669 set_proxy (SoupSession *session)
671 GConfClient *gconf_client;
672 char *host;
673 int port;
674 char *proxy_uri;
675 SoupURI *uri;
676 char *username = NULL;
677 char *password = NULL;
680 gconf_client = gconf_client_get_default ();
682 if (gconf_client_get_bool (gconf_client, USE_PROXY_KEY, NULL) == FALSE) {
683 g_object_unref (gconf_client);
684 return;
687 host = gconf_client_get_string (gconf_client, PROXY_HOST_KEY, NULL);
688 if (host == NULL) {
689 g_object_unref (gconf_client);
690 return;
692 port = gconf_client_get_int (gconf_client, PROXY_PORT_KEY, NULL);
693 if (port == 0)
694 port = 80;
696 if (gconf_client_get_bool (gconf_client, USE_PROXY_AUTH, NULL)) {
697 username = gconf_client_get_string (gconf_client, PROXY_USER, NULL);
698 password = gconf_client_get_string (gconf_client, PROXY_PASSWORD, NULL);
701 if (username && password)
702 proxy_uri = g_strdup_printf ("http://%s:%s@%s:%d", username, password, host, port);
703 else
704 proxy_uri = g_strdup_printf ("http://%s:%d", host, port);
706 uri = soup_uri_new (proxy_uri);
707 g_object_set (G_OBJECT (session), "proxy-uri", uri, NULL);
709 g_free (host);
710 g_free (username);
711 g_free (password);
712 g_free (proxy_uri);
713 soup_uri_free (uri);
714 g_object_unref (gconf_client);
719 static char*
720 create_report_title (BugzillaApplication *app, int type, const char *description)
722 char *title;
723 long size = 0;
724 char *tmp = NULL;
726 if (description) {
727 tmp = g_malloc0 (256); /* This should be safe enough for 24 UTF-8 chars.
728 * anyway, I miss a g_utf8_strndup :) */
729 size = g_utf8_strlen (description, -1);
730 if (size > 24) {
731 g_utf8_strncpy (tmp, description, 24);
732 } else {
733 g_utf8_strncpy (tmp, description, size);
737 if (type == BUG_TYPE_CRASH) {
738 title = g_strdup_printf ("crash in %s: %s%s", app->cname,
739 tmp ? tmp : "empty description",
740 (tmp && size > 24) ? "..." : "");
741 } else {
742 title = g_strdup_printf ("%s: %s%s", app->cname,
743 tmp ? tmp : "empty description",
744 (tmp && size > 24) ? "..." : "");
747 g_free (tmp);
749 return title;
751 static void
752 send_report (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui)
754 GtkTextView *text_view;
755 GtkTextBuffer *buffer;
756 GtkTextIter start;
757 GtkTextIter end;
758 int type;
759 char *gdb_text;
760 char *details_text;
761 char *title;
762 char *final_text;
763 const char *email;
764 SoupSession *session;
765 SoupMessage *message;
766 GError *err = NULL;
768 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check")));
770 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
771 buffer = gtk_text_view_get_buffer (text_view);
772 gtk_text_buffer_get_start_iter (buffer, &start);
773 gtk_text_buffer_get_end_iter (buffer, &end);
774 gdb_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
776 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "details-view"));
777 buffer = gtk_text_view_get_buffer (text_view);
778 gtk_text_buffer_get_start_iter (buffer, &start);
779 gtk_text_buffer_get_end_iter (buffer, &end);
780 details_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
782 type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(ui), "type"));
783 final_text = g_strdup_printf ("%s%s\n\n\n%s",
784 type == BUG_TYPE_CRASH ? "What were you doing when the application crashed?\n" : "",
785 details_text != NULL ? details_text : "",
786 gdb_text != NULL ? gdb_text : "<empty backtrace>");
788 email = gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry")));
789 title = create_report_title (app, type, details_text);
791 message = bugzilla_create_report (app, type, gnome_version, email, title, final_text, ui, gopt_data.minidump_file, &err);
792 if (message == NULL) {
793 char *text;
795 if (err != NULL) {
796 text = g_strdup_printf (_("Unable to create the bug report: %s\n"), err->message);
797 } else {
798 text = g_strdup_printf (_("There was an error creating the bug report\n"));
801 buddy_error (NULL, text);
802 g_free (text);
803 g_free (gdb_text);
804 g_free (details_text);
805 g_free (title);
806 g_free (final_text);
807 bug_buddy_quit (ui);
808 return;
811 session = soup_session_async_new ();
812 set_proxy (session);
815 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ui, "pending-reports-check")))) {
816 GDir *dir;
817 char *dirname;
818 GError *error = NULL;
820 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
821 dir = g_dir_open (dirname, 0, &error);
822 if (dir) {
823 const char *name = g_dir_read_name (dir);
824 while (name != NULL) {
825 char *path;
826 char *contents;
827 gsize length;
829 path = g_strdup_printf ("%s/%s", dirname, name);
830 if (g_file_get_contents (path, &contents, &length, NULL)) {
831 SoupMessage *msg;
832 msg = soup_message_new ("POST", "http://bugzilla.gnome.org/bugbuddy.cgi");
833 soup_message_set_request (msg, "text/xml",
834 SOUP_MEMORY_TAKE,
835 contents, length);
836 bug_count++;
837 soup_session_queue_message (session, SOUP_MESSAGE (msg),
838 (SoupSessionCallback)previous_sent, ui);
840 g_free (path);
841 name = g_dir_read_name (dir);
844 g_dir_close (dir);
848 bug_count++;
850 soup_session_queue_message (session, message,
851 (SoupSessionCallback)bug_sent, ui);
852 g_free (gdb_text);
853 g_free (details_text);
854 g_free (title);
855 g_free (final_text);
857 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
858 _("Sending..."));
862 /* A local part is valid if it is one or more valid characters. */
863 static gboolean
864 email_local_part_is_valid (const char *local_part)
866 const char *character;
868 if (!local_part[0])
869 return FALSE;
871 for (character = local_part; *character; character++) {
872 /* RFC 3696 says *any* printable ASCII character can
873 * appear in local-part, subject to quoting rules. */
874 if (g_ascii_isprint (*character))
875 continue;
877 /* Not valid character, not valid local part. */
878 return FALSE;
881 return TRUE;
884 /* A domain label is valid if it is one or more valid characters. */
885 static gboolean
886 email_domain_label_is_valid (const char *domain_label)
888 const char *character;
889 int i;
891 /* Validate each character, whilst measuring length, i. */
892 for (i = 0; *(character = domain_label + i); i++) {
894 /* If character is alphanumeric it is valid. */
895 if (g_ascii_isalnum (*character))
896 continue;
898 /* If it's a hyphen, it's also valid. */
899 if (*character == '-')
900 continue;
902 /* Anything else is invalid */
903 return FALSE;
906 /* Labels must be between 1 and 63 characters long */
907 if (i < 1 || i > 63) {
908 return FALSE;
911 return TRUE;
914 /* A domain is valid if it is one or more valid, dot-separated labels. */
915 static gboolean
916 email_domain_is_valid (const char *domain)
918 char **labels;
919 char **this_label;
920 gboolean retval = FALSE;
922 /* If there is no domain, there are no domain labels and the domain is
923 * not valid. */
924 if (!domain[0])
925 return FALSE;
927 /* Split the domain on the dot to validate labels. */
928 labels = g_strsplit (domain, ".", 0);
929 if (g_strv_length (labels) == 1) {
930 /* the domain doesn't contain any dot: it's not valid */
931 goto out;
934 for (this_label = labels; *this_label; this_label++) {
935 if (!email_domain_label_is_valid (*this_label)) {
936 goto out;
940 retval = TRUE;
942 out:
943 g_strfreev (labels);
944 return retval;
947 /* Check for *simple* email addresses of the form user@host, with checks
948 * in characters used, and sanity checks on the form of host.
950 /* FIXME: Should we provide a useful error message? */
951 static gboolean
952 email_is_valid (const char *address)
954 char *local_part;
955 char *domain;
956 char *at_sign;
957 gboolean is_valid;
959 /* Split on the *last* '@' character: */
960 at_sign = strrchr (address, '@');
962 if (at_sign == NULL)
963 return FALSE;
965 local_part = g_strndup (address, at_sign - address);
966 domain = g_strdup (at_sign + 1);
968 /* Check each part is valid */
969 is_valid = email_local_part_is_valid (local_part)
970 && email_domain_is_valid (domain);
972 g_free (local_part);
973 g_free (domain);
975 return is_valid;
978 static void
979 check_email (GtkEditable *editable, gpointer data)
981 const char *email;
982 GtkBuilder *ui = (GtkBuilder*) data;
984 email = gtk_entry_get_text (GTK_ENTRY (editable));
985 gtk_widget_set_sensitive (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")),
986 email_is_valid (email));
990 static void
991 on_send_clicked (GtkWidget *button, gpointer data)
993 BugzillaApplication *app;
994 GnomeVersionInfo *gnome_version;
995 GtkRequisition requisition;
996 GtkBuilder *ui = (GtkBuilder*) data;
997 GtkWidget *details;
998 int i;
1000 app = g_object_get_data (G_OBJECT (ui), "app");
1001 gnome_version = g_object_get_data (G_OBJECT (ui), "gnome-version");
1003 details = GTK_WIDGET (gtk_builder_get_object (ui, "details-view"));
1004 i = gtk_text_buffer_get_char_count (
1005 gtk_text_view_get_buffer (GTK_TEXT_VIEW (details)));
1007 if (i < MIN_REPORT_DETAILS_CHARS) {
1008 GtkWidget *dialog;
1009 GtkWidget *button;
1010 GtkWidget *icon;
1012 int i;
1014 dialog = gtk_message_dialog_new (NULL,
1015 GTK_DIALOG_MODAL,
1016 GTK_MESSAGE_INFO,
1017 GTK_BUTTONS_NONE,
1018 _("The description you provided for the "
1019 "crash is very short. Are you sure you want "
1020 "to send it?"));
1022 /* Secondary text */
1023 gtk_message_dialog_format_secondary_text
1024 (GTK_MESSAGE_DIALOG (dialog),
1025 _("A short description is probably not of much help "
1026 "to the developers investigating your report. "
1027 "If you provide a better one, for instance "
1028 "specifying a way to reproduce the crash, the "
1029 "issue can be more easily resolved."));
1031 /* Review button */
1032 button = gtk_dialog_add_button (GTK_DIALOG (dialog),
1033 _("_Review description"),
1034 GTK_RESPONSE_CANCEL);
1035 icon = gtk_image_new_from_stock
1036 (GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON);
1037 gtk_button_set_image (GTK_BUTTON (button), icon);
1038 gtk_widget_show (button);
1040 /* Send anyway button */
1041 button = gtk_dialog_add_button (GTK_DIALOG (dialog),
1042 _("_Send anyway"),
1043 GTK_RESPONSE_OK);
1044 icon = gtk_image_new_from_stock
1045 (GTK_STOCK_OK, GTK_ICON_SIZE_BUTTON);
1046 gtk_button_set_image (GTK_BUTTON (button), icon);
1047 gtk_widget_show (button);
1049 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
1050 GTK_RESPONSE_OK);
1052 i = gtk_dialog_run (GTK_DIALOG (dialog));
1054 gtk_widget_destroy (dialog);
1056 if (i != GTK_RESPONSE_OK)
1057 return;
1060 /* hide the send button immediately so that the user can't click
1061 * it more than once (this will create multiple bugs).
1063 gtk_widget_hide (GTK_WIDGET (
1064 gtk_builder_get_object (ui, "send-button")));
1066 gtk_widget_show (GTK_WIDGET (
1067 gtk_builder_get_object (ui, "progressbar")));
1068 gtk_widget_hide (GTK_WIDGET (
1069 gtk_builder_get_object (ui, "final-box")));
1070 gtk_widget_hide (GTK_WIDGET (
1071 gtk_builder_get_object (ui, "review-box")));
1073 gtk_widget_size_request (GTK_WIDGET (
1074 gtk_builder_get_object (ui, "main-window")),
1075 &requisition);
1077 gtk_window_resize (GTK_WINDOW (
1078 gtk_builder_get_object (ui, "main-window")),
1079 requisition.width, requisition.height);
1081 send_report (app, gnome_version, ui);
1085 static gboolean
1086 gdb_insert_text (const gchar *stacktrace, GtkBuilder *ui)
1088 GtkTextView *text_view;
1089 GtkTextIter end;
1090 GtkTextBuffer *buffer;
1092 /* FIXME: These strings are gdb specific, we should add here also dbx */
1093 const char *bt_step1 = "#1";
1094 const char *bt_step2 = "#2";
1095 const char *bt_step3 = "#3";
1097 if (!g_strrstr (stacktrace, bt_step1) &&
1098 !g_strrstr (stacktrace, bt_step2) &&
1099 !g_strrstr (stacktrace, bt_step3)) {
1100 return FALSE;
1105 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1106 buffer = gtk_text_view_get_buffer (text_view);
1107 gtk_text_buffer_get_end_iter (buffer, &end);
1109 /* add the stacktrace to the GtkTextView */
1110 gtk_text_buffer_insert (buffer, &end, stacktrace, strlen (stacktrace));
1112 return TRUE;
1115 static void
1116 show_pending_checkbox_if_pending (GtkBuilder *ui)
1118 char *dirname;
1119 GtkWidget *check;
1121 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
1122 if (g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
1123 check = GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check"));
1124 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE);
1125 gtk_widget_show (check);
1127 g_free (dirname);
1130 static GtkWidget*
1131 create_debuginfo_link (void)
1133 GtkWidget *urlbutton;
1135 /* create a clickable link to the bug report */
1136 urlbutton = gtk_link_button_new_with_label ("http://live.gnome.org/GettingTraces/DistroSpecificInstructions",
1137 /* Translators: This is the hyperlink which takes to http://live.gnome.org/GettingTraces/DistroSpecificInstructions
1138 * page. Please also mention that the page is in English */
1139 _("Getting useful crash reports"));
1140 g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL);
1142 return urlbutton;
1146 static void
1147 add_minidump_text_output (const char *minidump_file, GtkBuilder *ui)
1149 GtkTextView *text_view;
1150 GtkTextIter end;
1151 GtkTextBuffer *buffer;
1152 gchar *command_line;
1153 gchar *standard_output, *standard_error;
1154 gint exit_status;
1155 GError *error;
1158 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1159 buffer = gtk_text_view_get_buffer (text_view);
1160 gtk_text_buffer_get_end_iter (buffer, &end);
1162 command_line = g_strdup_printf ("minidump_dump %s", minidump_file);
1163 if (g_spawn_command_line_sync (command_line, &standard_output, &standard_error,
1164 &exit_status, &error)) {
1165 gtk_text_buffer_insert (buffer, &end, standard_output, strlen (standard_output));
1166 g_free (standard_output);
1167 g_free (standard_error);
1168 } else {
1169 g_error_free (error);
1178 static void
1179 useless_finished (GtkBuilder *ui)
1182 GtkWidget *button, *image, *main_vbox, *urlbutton;
1183 BugzillaApplication *app;
1184 char *label_text;
1186 app = g_object_get_data (G_OBJECT (ui), "app");
1188 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1190 label_text = g_markup_printf_escaped (_("The application %s crashed. The bug reporting tool was "
1191 "unable to collect enough information about the crash to be "
1192 "useful to the developers.\n\n"
1193 "In order to submit useful reports, please consider installing "
1194 "debug packages for your distribution.\n"
1195 "Click the link below to get information about how to install "
1196 "these packages:\n"),
1197 app->name);
1198 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
1199 label_text);
1201 main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox"));
1202 urlbutton = create_debuginfo_link ();
1203 gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0);
1205 gtk_widget_show (urlbutton);
1207 g_free (label_text);
1209 button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
1210 gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
1211 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
1213 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON),
1214 gtk_button_set_image (GTK_BUTTON (button), image);
1216 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "email-entry")));
1220 static void
1221 known_app_finished (GtkBuilder *ui)
1223 BugzillaApplication *app;
1224 GtkWidget *email_entry;
1225 GtkWidget *button;
1226 char *default_email;
1227 char *lang_note, *label_text, *s;
1228 const char *en_lang_note = N_("\n\nPlease write your report in English, if possible.");
1230 app = g_object_get_data (G_OBJECT (ui), "app");
1232 fill_custom_info (app, ui);
1233 fill_stderr_info (ui);
1235 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1236 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1237 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1239 lang_note = gettext (en_lang_note);
1241 label_text = g_strconcat (_("Information about the %s application crash has been successfully collected. "
1242 "Please provide some more details about what you were doing when "
1243 "the application crashed.\n\n"
1245 "A valid email address is required. This will allow the developers to "
1246 "contact you for more information if necessary."),
1247 strcmp (lang_note, en_lang_note) ? lang_note : NULL,
1248 NULL);
1250 s = g_markup_printf_escaped (label_text, app->name);
1252 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
1255 g_free (s);
1256 g_free (label_text);
1258 show_pending_checkbox_if_pending (ui);
1260 button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button"));
1261 g_signal_connect (button, "clicked",
1262 G_CALLBACK (on_send_clicked), ui);
1264 email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"));
1265 g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui);
1267 default_email = get_default_user_email ();
1269 if (default_email != NULL) {
1270 gtk_entry_set_text (GTK_ENTRY (email_entry), default_email);
1271 g_free (default_email);
1272 } else {
1273 gtk_widget_set_sensitive (button, FALSE);
1276 if (search_forbidden_words (ui)) {
1277 char *review_text = g_markup_printf_escaped ("<small><i><span weight=\"bold\">%s</span> %s</i></small>",
1278 _("WARNING:"),
1279 _("Some sensitive data is likely present in the crash details. "
1280 "Please review and edit the information if you are concerned "
1281 "about transmitting passwords or other sensitive data."));
1282 gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "review-label")), review_text);
1284 g_signal_connect (gtk_builder_get_object (ui, "review-button"), "clicked", G_CALLBACK (show_review), ui);
1285 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-box")));
1287 gtk_widget_grab_focus (GTK_WIDGET (gtk_builder_get_object (ui, "details-view")));
1290 static void
1291 gdb_finished (const gchar *stacktrace, gpointer data)
1293 GtkBuilder *ui = (GtkBuilder*) data;
1295 if (gdb_insert_text (stacktrace, ui)) {
1296 known_app_finished (ui);
1297 } else {
1298 useless_finished (ui);
1304 static void
1305 on_save_clicked (GtkWidget *button, gpointer user_data)
1307 GtkBuilder *ui = (GtkBuilder *)user_data;
1308 GtkWidget *dialog;
1309 const char *desktop;
1310 char *filename;
1311 gboolean desktop_is_home_dir, saved;
1312 GConfClient *gconf_client;
1314 saved = FALSE;
1316 dialog = gtk_file_chooser_dialog_new (_("Save File"),
1317 GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
1318 GTK_FILE_CHOOSER_ACTION_SAVE,
1319 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1320 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1321 NULL);
1323 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
1325 gconf_client = gconf_client_get_default ();
1326 desktop_is_home_dir = gconf_client_get_bool (gconf_client, DESKTOP_IS_HOME_DIR, NULL);
1327 g_object_unref (gconf_client);
1329 if (desktop_is_home_dir)
1330 desktop = g_get_home_dir();
1331 else
1332 desktop = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
1334 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), desktop);
1336 filename = g_strconcat (gopt_data.app_file, _("-bugreport.txt"), NULL);
1337 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
1338 g_free (filename);
1340 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
1341 char *filename;
1342 GtkTextView *text_view;
1343 GtkTextBuffer *buffer;
1344 GtkTextIter start;
1345 GtkTextIter end;
1346 gchar *text;
1348 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1349 buffer = gtk_text_view_get_buffer (text_view);
1350 gtk_text_buffer_get_start_iter (buffer, &start);
1351 gtk_text_buffer_get_end_iter (buffer, &end);
1352 text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1354 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1355 save_to_file (filename, text);
1356 g_free (filename);
1357 g_free (text);
1358 saved = TRUE;
1361 gtk_widget_destroy (dialog);
1362 if (saved) {
1363 bug_buddy_quit (ui);
1367 static void
1368 focus_details (GtkWidget *widget, gpointer data)
1370 gtk_widget_grab_focus (widget);
1375 static void
1376 unknown_app_finished (GtkBuilder *ui)
1378 GtkWidget *button;
1379 char *label_text;
1381 fill_stderr_info (ui);
1383 if (gopt_data.minidump_file) {
1384 add_minidump_text_output (gopt_data.minidump_file, ui);
1387 /* don't need user input, so hide these widgets */
1388 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1389 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1391 /* make the send button into a save button :-) */
1392 button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button"));
1393 gtk_button_set_label (GTK_BUTTON (button), _("_Save Bug Report"));
1394 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
1395 g_signal_connect (GTK_BUTTON (button), "clicked", G_CALLBACK (on_save_clicked), ui);
1396 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1398 label_text = g_markup_printf_escaped (_("The application %s has crashed.\n"
1399 "Information about the crash has been successfully collected.\n\n"
1400 "This application is not known to bug-buddy, therefore the "
1401 "bug report cannot be sent to the GNOME Bugzilla. Please save the "
1402 "bug to a text file and report it to the appropriate bug tracker "
1403 "for this application."), gopt_data.app_file);
1404 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), label_text);
1406 /* FIXME: If we just grab the focus here to the GtkTextView it will crash on the blink_cb because
1407 * the window is nop mapped! Is this a gtk+ bug? are we doing something wrong?
1408 * Let's do a funny Workaround: */
1409 g_signal_connect_after (gtk_builder_get_object (ui, "details-view"), "realize", G_CALLBACK (focus_details), NULL);
1410 gtk_widget_realize (GTK_WIDGET (gtk_builder_get_object (ui, "details-view")));
1416 static void
1417 gdb_finished_unknown_app (const gchar *stacktrace, gpointer data)
1419 GtkBuilder *ui = (GtkBuilder*) data;
1421 gdb_insert_text (stacktrace, ui);
1422 unknown_app_finished (ui);
1425 static void
1426 bug_buddy_quit (GtkBuilder *ui)
1428 gpointer data;
1430 g_return_if_fail (ui != NULL);
1432 data = g_object_get_data (G_OBJECT (ui), "sourceid");
1434 if (data != NULL) {
1435 guint source_id = GPOINTER_TO_UINT (data);
1437 /* removes the context from the main loop and kills any remaining
1438 * gdb process */
1439 if (source_id > 0) {
1440 g_source_remove (source_id);
1441 g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (0));
1445 g_hash_table_destroy (apps);
1447 g_object_unref (ui);
1449 gtk_main_quit ();
1452 static gboolean
1453 keypress_callback (GtkWidget *widget, GdkEventKey *event, gpointer data)
1455 if (event->keyval == GDK_Escape) {
1456 close_callback (NULL, data);
1457 return TRUE;
1459 /* let others handle the event */
1460 return FALSE;
1463 static void
1464 close_callback (GtkWidget *widget, gpointer user_data)
1466 GtkBuilder *ui = (GtkBuilder *)user_data;
1468 bug_buddy_quit (ui);
1471 static void
1472 help_callback (GtkWidget *widget, gpointer user_data)
1474 GnomeProgram *program = (GnomeProgram *)user_data;
1475 GError *error = NULL;
1477 gnome_help_display_desktop (program,
1478 "user-guide",
1479 "user-guide",
1480 "feedback-bugs",
1481 &error);
1483 if (error) {
1484 GtkWidget *error_dialog =
1485 gtk_message_dialog_new (NULL,
1486 GTK_DIALOG_MODAL,
1487 GTK_MESSAGE_ERROR,
1488 GTK_BUTTONS_CLOSE,
1489 _("There was an error displaying help: %s"),
1490 error->message);
1492 g_signal_connect (G_OBJECT (error_dialog), "response",
1493 G_CALLBACK (gtk_widget_destroy), NULL);
1495 gtk_window_set_resizable (GTK_WINDOW (error_dialog), FALSE);
1497 gtk_widget_show (error_dialog);
1498 g_error_free (error);
1499 error = NULL;
1503 static gboolean
1504 delete_callback (GtkWidget *widget, GdkEvent *event, gpointer data)
1506 close_callback (NULL, data);
1507 return TRUE;
1510 static void
1511 fill_gnome_info (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui)
1513 char *version_info;
1514 char *distro;
1515 GtkTextView *text_view;
1516 GtkTextIter end;
1517 GtkTextBuffer *buffer;
1519 g_return_if_fail (app != NULL);
1520 g_return_if_fail (gnome_version != NULL);
1521 g_return_if_fail (ui != NULL);
1523 distro = get_distro_name ();
1524 version_info = g_strdup_printf ("Distribution: %s\n"
1525 "Gnome Release: %s %s (%s)\n"
1526 "BugBuddy Version: %s\n"
1527 "\n",
1528 distro,
1529 gnome_version->gnome_platform, gnome_version->gnome_date,
1530 gnome_version->gnome_distributor, VERSION);
1532 g_free (distro);
1534 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1535 buffer = gtk_text_view_get_buffer (text_view);
1536 gtk_text_buffer_get_end_iter (buffer, &end);
1537 gtk_text_buffer_insert (buffer, &end, version_info, strlen (version_info));
1539 g_free (version_info);
1542 static void
1543 fill_custom_info (BugzillaApplication *app, GtkBuilder *ui)
1545 GtkTextView *text_view;
1546 GtkTextIter end;
1547 GtkTextBuffer *buffer;
1548 gint status;
1549 gchar *output;
1550 gchar *standard_output = NULL;
1551 gchar *standard_error = NULL;
1552 GError *error = NULL;
1554 g_return_if_fail (app != NULL);
1555 g_return_if_fail (ui != NULL);
1557 if (app->extra_info_script == NULL) {
1558 return;
1561 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1562 buffer = gtk_text_view_get_buffer (text_view);
1563 gtk_text_buffer_get_end_iter (buffer, &end);
1565 if (!g_spawn_command_line_sync (app->extra_info_script, &standard_output, &standard_error,
1566 &status, &error)) {
1567 gchar *error_string = g_strdup_printf ("There was an error running \"%s\" script:\n"
1568 "%s", app->extra_info_script, error->message);
1569 gtk_text_buffer_insert (buffer, &end, error_string, strlen (error_string));
1571 g_free (error);
1572 g_free (error_string);
1573 return;
1576 output = g_strdup_printf ("Output of custom script \"%s\":\n"
1577 "%s\n\n",
1578 app->extra_info_script,
1579 standard_output ? standard_output : "");
1581 gtk_text_buffer_insert (buffer, &end, output, strlen (output));
1583 g_free (output);
1584 g_free (standard_output);
1585 g_free (standard_error);
1589 static void
1590 fill_proccess_info (pid_t pid, GtkBuilder *ui)
1592 GtkTextView *text_view;
1593 GtkTextIter end;
1594 GtkTextBuffer *buffer;
1595 char *mem;
1596 char *time;
1597 char *proccess_info;
1599 mem = proccess_get_mem_state (pid);
1600 time = proccess_get_time (pid);
1602 proccess_info = g_strdup_printf ("%s\n"
1603 "%s\n"
1604 "\n",
1605 mem, time);
1607 g_free (mem);
1608 g_free (time);
1610 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1611 buffer = gtk_text_view_get_buffer (text_view);
1612 gtk_text_buffer_get_end_iter (buffer, &end);
1613 gtk_text_buffer_insert (buffer, &end, proccess_info, strlen (proccess_info));
1615 g_free (proccess_info);
1619 static void
1620 fill_include_file (char *filename, GtkBuilder *ui)
1622 GtkTextView *text_view;
1623 GtkTextIter end;
1624 GtkTextBuffer *buffer;
1625 char *text;
1626 GError *error = NULL;
1628 if (g_file_get_contents (filename, &text, NULL, &error) != TRUE) {
1629 buddy_error (NULL, error->message);
1630 g_error_free (error);
1631 return;
1634 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1635 buffer = gtk_text_view_get_buffer (text_view);
1636 gtk_text_buffer_get_end_iter (buffer, &end);
1637 gtk_text_buffer_insert (buffer, &end, text, strlen (text));
1639 g_free (text);
1644 static void
1645 fill_system_info (GtkBuilder *ui)
1647 GConfClient *gconf_client;
1648 GtkTextView *text_view;
1649 GtkTextIter end;
1650 GtkTextBuffer *buffer;
1651 GString *system_info;
1652 struct utsname uts_buf;
1653 char *str;
1654 gboolean has_selinux, enforcing, a11y;
1656 g_return_if_fail (ui != NULL);
1658 system_info = g_string_new ("");
1660 if (uname (&uts_buf) == 0) {
1661 g_string_append_printf (system_info, "System: %s %s %s %s\n", uts_buf.sysname, uts_buf.release, uts_buf.version, uts_buf.machine);
1664 /* X server checks */
1665 g_string_append_printf (system_info, "X Vendor: %s\n", ServerVendor (gdk_display));
1666 g_string_append_printf (system_info, "X Vendor Release: %d\n", VendorRelease (gdk_display));
1669 /* Selinux checks */
1670 has_selinux = FALSE;
1671 if (g_file_get_contents ("/proc/filesystems", &str, NULL, NULL)) {
1672 has_selinux = strstr (str, "selinuxfs") != NULL;
1673 g_free (str);
1675 if (has_selinux) {
1676 enforcing = TRUE;
1677 if (g_file_get_contents ("/selinux/enforce", &str, NULL, NULL)) {
1678 enforcing = strcmp (str, "0") != 0;
1679 g_free (str);
1681 g_string_append_printf (system_info, "Selinux: %s\n", enforcing?"Enforcing":"Permissive");
1682 } else {
1683 g_string_append_printf (system_info, "Selinux: No\n");
1686 /* A11y and gtk */
1687 gconf_client = gconf_client_get_default ();
1688 a11y = gconf_client_get_bool (gconf_client, ACCESSIBILITY_KEY, NULL);
1689 g_string_append_printf (system_info, "Accessibility: %s\n", a11y?"Enabled":"Disabled");
1690 str = gconf_client_get_string (gconf_client, GTK_THEME_KEY, NULL);
1691 g_string_append_printf (system_info, "GTK+ Theme: %s\n", str);
1692 g_free (str);
1693 str = gconf_client_get_string (gconf_client, ICON_THEME_KEY, NULL);
1694 g_string_append_printf (system_info, "Icon Theme: %s\n", str);
1695 g_free (str);
1696 g_object_unref (gconf_client);
1698 g_string_append (system_info, "\n");
1700 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1701 buffer = gtk_text_view_get_buffer (text_view);
1702 gtk_text_buffer_get_end_iter (buffer, &end);
1703 gtk_text_buffer_insert (buffer, &end, system_info->str, system_info->len);
1705 g_string_free (system_info, TRUE);
1708 static void
1709 fill_stderr_info (GtkBuilder *ui)
1711 GtkTextView *text_view;
1712 GtkTextIter end;
1713 GtkTextBuffer *buffer;
1714 GString *stderr_info;
1715 char *str, *file;
1716 gchar **lines;
1717 int n_lines, i;
1719 g_return_if_fail (ui != NULL);
1721 stderr_info = g_string_new ("");
1723 /* .xsession-errors: read file */
1724 file = g_build_filename (g_get_home_dir (), ".xsession-errors", NULL);
1725 if (g_file_get_contents (file, &str, NULL, NULL)) {
1726 lines = g_strsplit (str, "\n", -1);
1727 g_free (str);
1728 n_lines = 0;
1729 while (lines[n_lines] != NULL) {
1730 n_lines++;
1733 if (n_lines > 0) {
1734 struct stat buf;
1735 char *mtime_age = NULL;
1736 time_t age = 0;
1739 if (stat (file, &buf) == 0) {
1740 age = time (NULL) - buf.st_mtime;
1741 if (age > 5) {
1742 mtime_age = g_strdup_printf (" (%d sec old)", (int) age);
1746 g_string_append_printf (stderr_info,
1747 "\n\n----------- .xsession-errors%s ---------------------\n",
1748 mtime_age?mtime_age:"");
1749 g_free (mtime_age);
1752 for (i = MAX (0, n_lines-16); i < n_lines; i++) {
1753 if (lines[i][0] != 0) {
1754 /* Limit line length to 200 chars to avoid excessive data */
1755 if (strlen (lines[i]) > 200) {
1756 lines[i][200] = 0;
1759 g_string_append_printf (stderr_info, "%s\n", lines[i]);
1762 if (n_lines > 0)
1763 g_string_append (stderr_info, "--------------------------------------------------\n");
1765 g_strfreev (lines);
1767 g_free (file);
1769 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1770 buffer = gtk_text_view_get_buffer (text_view);
1771 gtk_text_buffer_get_end_iter (buffer, &end);
1772 gtk_text_buffer_insert (buffer, &end, stderr_info->str, stderr_info->len);
1774 g_string_free (stderr_info, TRUE);
1778 main (int argc, char *argv[])
1780 gchar *s;
1781 BugzillaApplication *app;
1782 GnomeVersionInfo *gnome_version;
1783 guint progress;
1784 GtkWidget *main_window;
1785 GOptionContext *context;
1786 GnomeProgram *program;
1787 guint source_id;
1788 GError *err = NULL;
1789 GtkBuilder *ui = NULL;
1791 memset (&gopt_data, 0, sizeof (gopt_data));
1793 bindtextdomain (PACKAGE, GNOMELOCALEDIR);
1794 bind_textdomain_codeset (PACKAGE, "UTF-8");
1795 textdomain (PACKAGE);
1797 context = g_option_context_new (N_("\n\nBug Buddy is a utility that helps report debugging\n"
1798 "information to the GNOME Bugzilla when a program crashes."));
1800 g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
1802 g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
1804 program = gnome_program_init (PACKAGE, VERSION,
1805 LIBGNOMEUI_MODULE,
1806 argc, argv,
1807 GNOME_PARAM_GOPTION_CONTEXT, context,
1808 GNOME_PARAM_APP_DATADIR, BUDDY_DATADIR,
1809 GNOME_CLIENT_PARAM_SM_CONNECT, FALSE,
1810 NULL);
1812 g_set_application_name (_("Bug Buddy"));
1813 gtk_window_set_default_icon_name ("bug-buddy");
1816 s = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_APP_DATADIR,
1817 "bug-buddy.gtkbuilder", TRUE, NULL);
1818 if (s) {
1819 ui = gtk_builder_new ();
1820 gtk_builder_add_from_file (ui, s, &err);
1821 gtk_builder_set_translation_domain (ui, GETTEXT_PACKAGE);
1824 g_free (s);
1826 if (!ui || err) {
1827 buddy_error (NULL,
1828 _("Bug Buddy could not load its user interface file.\n"
1829 "Please make sure Bug Buddy was installed correctly."));
1830 if (err) {
1831 g_error_free (err);
1833 g_object_unref (program);
1834 return 0;
1837 main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window"));
1838 g_signal_connect (main_window, "delete-event", G_CALLBACK (delete_callback), ui);
1839 g_signal_connect (main_window, "key-press-event", G_CALLBACK (keypress_callback), ui);
1841 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1843 progress = g_timeout_add (100, update_progress_bar,
1844 gtk_builder_get_object (ui, "progressbar"));
1845 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
1846 _("Collecting information from your system..."));
1848 if (gopt_data.app_file == NULL && gopt_data.package == NULL) {
1849 buddy_error (NULL, _("Either --appname or --package arguments are required.\n"));
1850 g_object_unref (program);
1851 return 0;
1854 if (gopt_data.app_file && gopt_data.pid == 0 &&
1855 gopt_data.include_file == NULL &&
1856 gopt_data.minidump_file == NULL) {
1857 buddy_error (NULL, _("Either --pid , --include or --minidump arguments are required.\n"));
1858 g_object_unref (program);
1859 return 0;
1862 /* get some information about the gnome version */
1863 gnome_version = get_gnome_version_info ();
1864 if (gnome_version == NULL) {
1865 buddy_error (NULL, _("Bug Buddy was unable to retrieve information regarding "
1866 "the version of GNOME you are running. This is most likely "
1867 "due to a missing installation of gnome-desktop.\n"));
1868 g_object_unref (program);
1869 return 0;
1872 g_object_set_data (G_OBJECT (ui), "gnome-version", gnome_version);
1874 /* connect the signal handler for the help button */
1875 g_signal_connect (gtk_builder_get_object (ui, "help-button"), "clicked",
1876 G_CALLBACK (help_callback), program);
1878 gtk_widget_show (main_window);
1880 apps = load_applications ();
1882 /* If we have a binary file it is a crash */
1883 if (gopt_data.app_file) {
1884 app = g_hash_table_lookup (apps, gopt_data.app_file);
1886 /* we handle an unknown application (no .desktop file) differently */
1887 if (app != NULL) {
1888 s = g_markup_printf_escaped (_("The %s application has crashed. "
1889 "We are collecting information about the crash to send to the "
1890 "developers in order to fix the problem."), app->name);
1891 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s);
1892 g_free (s);
1894 g_object_set_data (G_OBJECT (ui), "app", app);
1896 if (app->icon) {
1897 gtk_image_set_from_icon_name (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")),
1898 app->icon,
1899 GTK_ICON_SIZE_DIALOG);
1901 fill_gnome_info (app, gnome_version, ui);
1904 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
1905 _("Collecting information from the crash..."));
1907 fill_system_info (ui);
1909 fill_proccess_info (gopt_data.pid, ui);
1911 if (gopt_data.pid > 0) {
1912 /* again, if this is an unknown application, we connect a different callback that
1913 * will allow the user to save the trace rather than sending it to the GNOME Bugzilla */
1914 if (app == NULL) {
1915 source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui,
1916 gdb_finished_unknown_app, &err);
1917 } else {
1918 source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui, gdb_finished, &err);
1921 if (source_id == 0) {
1922 buddy_error (NULL, _("Bug Buddy encountered the following error when trying "
1923 "to retrieve debugging information: %s\n"), err->message);
1924 g_error_free (err);
1925 g_object_unref (program);
1926 return 0;
1929 /* connect the close button callback so that we can remove the source from
1930 * the main loop (and kill gdb) if the user wants to quit before gdb is finished */
1931 g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (source_id));
1932 } else {
1933 if (app == NULL) {
1934 unknown_app_finished (ui);
1935 } else {
1936 known_app_finished (ui);
1939 g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_CRASH));
1940 } else {
1941 /* No binary file, so this is a non-crashing bug. Look the application from the --package arg */
1942 GtkWidget *email_entry;
1943 char *default_email;
1944 char *msg, *lang_note;
1945 const char *en_lang_note = N_("\n\nPlease write your report in English, if possible.");
1947 app = g_hash_table_find (apps, (GHRFunc)bugzilla_search_for_package, gopt_data.package);
1948 if (app == NULL) {
1949 /* Fallback to binary name */
1950 app = g_hash_table_lookup (apps, gopt_data.package);
1952 if (app == NULL) {
1953 buddy_error (NULL, _("Bug Buddy doesn't know how to send a suggestion for the application %s.\n"),
1954 gopt_data.package);
1955 return 0;
1958 g_object_set_data (G_OBJECT (ui), "app", app);
1960 if (app->icon) {
1961 gtk_image_set_from_icon_name (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")),
1962 app->icon,
1963 GTK_ICON_SIZE_DIALOG);
1965 fill_gnome_info (app, gnome_version, ui);
1966 fill_custom_info (app, ui);
1968 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1969 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1970 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1972 lang_note = gettext (en_lang_note);
1973 msg = g_strconcat (_("Thank you for helping us improving our software.\n"
1974 "Please fill your suggestions/error information for %s application.\n\n"
1975 "A valid email address is required. This will allow developers to "
1976 "contact you for more information if necessary."),
1977 strcmp (lang_note, en_lang_note)? lang_note: NULL,
1978 NULL);
1979 s = g_markup_printf_escaped (msg, app->name);
1980 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s);
1981 g_free (s);
1982 g_free (msg);
1984 s = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>",
1985 _("Suggestion / Error description:"));
1986 gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "main-label")), s);
1987 g_free (s);
1989 show_pending_checkbox_if_pending (ui);
1990 g_signal_connect (gtk_builder_get_object (ui, "send-button"), "clicked",
1991 G_CALLBACK (on_send_clicked), ui);
1993 email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"));
1994 g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui);
1996 default_email = get_default_user_email ();
1998 if (default_email != NULL) {
1999 gtk_entry_set_text (GTK_ENTRY (email_entry), default_email);
2000 g_free (default_email);
2002 g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_REQUEST));
2005 if (gopt_data.include_file != NULL) {
2006 fill_include_file (gopt_data.include_file, ui);
2010 g_signal_connect (gtk_builder_get_object (ui, "close-button"), "clicked",
2011 G_CALLBACK (close_callback), ui);
2013 gtk_main ();
2015 g_object_unref (program);
2017 return 0;