Don't connect close button twice. Fixes bug #471425
[bug-buddy.git] / src / bug-buddy.c
blob0ff41f34724e338f3a31241ede348a26b81f6553
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 <gnome.h>
45 #include <libgnomeui/gnome-window-icon.h>
46 #include <libgnomevfs/gnome-vfs-utils.h>
47 #include <gtk/gtkbuilder.h>
48 #include <gdk-pixbuf/gdk-pixbuf.h>
49 #include <libgnomecanvas/gnome-canvas-pixbuf.h>
50 #include <libgnome/libgnometypebuiltins.h>
51 #include <gdk/gdkx.h>
53 #include <libxml/tree.h>
54 #include <libxml/parser.h>
56 #include <gconf/gconf-client.h>
58 #ifdef HAVE_NETWORKMANAGER
59 #include <libnm_glib.h>
60 #endif
62 #include <libsoup/soup.h>
63 #include <libsoup/soup-xmlrpc-message.h>
65 #include <sys/types.h>
66 #include <signal.h>
68 #define d(x)
70 #define USE_PROXY_KEY "/system/http_proxy/use_http_proxy"
71 #define PROXY_HOST_KEY "/system/http_proxy/host"
72 #define PROXY_PORT_KEY "/system/http_proxy/port"
73 #define USE_PROXY_AUTH "/system/http_proxy/use_authentication"
74 #define PROXY_USER "/system/http_proxy/authentication_user"
75 #define PROXY_PASSWORD "/system/http_proxy/authentication_password"
76 #define ACCESSIBILITY_KEY "/desktop/gnome/interface/accessibility"
77 #define GTK_THEME_KEY "/desktop/gnome/interface/gtk_theme"
78 #define ICON_THEME_KEY "/desktop/gnome/interface/icon_theme"
79 #define DESKTOP_IS_HOME_DIR "/apps/nautilus/preferences/desktop_is_home_dir"
81 static GOptionData gopt_data;
82 static int bug_count = 0;
83 static GHashTable *apps = NULL;
85 static const GOptionEntry options[] = {
86 { "name", '\0', 0, G_OPTION_ARG_STRING, &gopt_data.name, N_("Name of contact"), N_("NAME") },
87 { "email", '\0', 0, G_OPTION_ARG_STRING, &gopt_data.email, N_("Email address of contact"), N_("EMAIL") },
88 { "package", '\0', 0, G_OPTION_ARG_STRING, &gopt_data.package, N_("Package containing the program"), N_("PACKAGE") },
89 { "package-ver",'\0', 0, G_OPTION_ARG_STRING, &gopt_data.package_ver, N_("Version of the package"), N_("VERSION") },
90 { "appname", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.app_file, N_("File name of crashed program"), N_("FILE") },
91 { "pid", '\0', 0, G_OPTION_ARG_INT, &gopt_data.pid, N_("PID of crashed program"), N_("PID") },
92 { "core", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.core_file, N_("Core file from program"), N_("FILE") },
93 { "include", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.include_file, N_("Text file to include in the report"), N_("FILE") },
94 { "minidump", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.minidump_file, N_("MiniDump file with info about the crash"), N_("FILE") },
95 { "kill", '\0', 0, G_OPTION_ARG_INT, &gopt_data.kill, N_("PID of the program to kill after the report"), N_("KILL") },
96 { NULL }
99 enum {
100 NETWORK_CONNECTED,
101 NETWORK_DISCONNECTED,
102 NETWORK_UNKNOWN
105 static void fill_stderr_info (GtkBuilder *ui);
106 static void fill_custom_info (BugzillaApplication *app, GtkBuilder *ui);
107 static void close_callback (GtkWidget *widget, gpointer user_data);
108 static void bug_buddy_quit (GtkBuilder *ui);
111 static void
112 buddy_error (GtkWidget *parent, const char *msg, ...)
114 GtkWidget *w;
115 GtkDialog *d;
116 gchar *s;
117 va_list args;
119 /* No va_list version of dialog_new, construct the string ourselves. */
120 va_start (args, msg);
121 s = g_strdup_vprintf (msg, args);
122 va_end (args);
124 w = gtk_message_dialog_new (GTK_WINDOW (parent),
126 GTK_MESSAGE_ERROR,
127 GTK_BUTTONS_OK,
128 "%s",
130 d = GTK_DIALOG (w);
131 gtk_dialog_set_default_response (d, GTK_RESPONSE_OK);
132 gtk_dialog_run (d);
133 gtk_widget_destroy (w);
134 g_free (s);
137 static void
138 lock_text (GtkBuilder *ui)
140 GtkTextView *text_view;
141 GtkTextBuffer *buffer;
142 GtkTextIter start;
143 GtkTextIter end;
144 static GtkTextTag *tag = NULL;
145 char *text;
147 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
148 buffer = gtk_text_view_get_buffer (text_view);
149 gtk_text_buffer_get_start_iter (buffer, &start);
150 gtk_text_buffer_get_end_iter (buffer, &end);
151 text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
153 if (!tag) {
154 GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (text_view));
155 tag = gtk_text_buffer_create_tag (buffer, "lock_tag",
156 "editable", FALSE,
157 /* I don't like how it looks like dimming also fg
158 "foreground-gdk", &style->fg[GTK_STATE_INSENSITIVE], */
159 "background-gdk", &style->bg[GTK_STATE_INSENSITIVE],
160 NULL);
163 if (gtk_text_iter_forward_search (&start, "Backtrace was generated from",
164 GTK_TEXT_SEARCH_TEXT_ONLY,
165 NULL, &end, NULL)) {
166 gtk_text_iter_forward_line (&end);
167 gtk_text_buffer_apply_tag_by_name (buffer, "lock_tag", &start, &end);
172 static gboolean
173 search_forbidden_words (GtkBuilder *ui)
175 GtkTextView *text_view;
176 GtkTextBuffer *buffer;
177 int i;
178 gboolean found = FALSE;
179 static GtkTextTag *tag = NULL;
181 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
182 buffer = gtk_text_view_get_buffer (text_view);
183 if (!tag) {
184 tag = gtk_text_buffer_create_tag (buffer, "forbbiden_tag",
185 "foreground", "white",
186 "background", "blue",
187 NULL);
190 for (i = 0; forbidden_words[i]; i++) {
191 GtkTextIter start;
192 GtkTextIter end;
194 gtk_text_buffer_get_start_iter (buffer, &start);
195 while (gtk_text_iter_forward_search (&start, forbidden_words[i],
196 GTK_TEXT_SEARCH_TEXT_ONLY,
197 &start, &end, NULL)) {
198 gtk_text_buffer_apply_tag_by_name (buffer, "forbbiden_tag", &start, &end);
199 start = end;
200 found = TRUE;
204 return found;
209 static void
210 copy_review (GtkWidget *button, gpointer data)
212 GtkTextView *text_view;
213 GtkTextBuffer *buffer;
214 GtkTextIter start;
215 GtkTextIter end;
216 GtkBuilder *ui = (GtkBuilder*) data;
218 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
219 buffer = gtk_text_view_get_buffer (text_view);
220 gtk_text_buffer_get_start_iter (buffer, &start);
221 gtk_text_buffer_get_end_iter (buffer, &end);
222 gtk_text_buffer_select_range (buffer, &start, &end);
223 gtk_text_buffer_copy_clipboard (buffer, gtk_clipboard_get (GDK_NONE));
227 static void
228 edit_review (GtkWidget *button, gpointer data)
230 GtkTextView *text_view;
231 GtkBuilder *ui = (GtkBuilder*) data;
232 gboolean editable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
234 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
235 gtk_text_view_set_editable (text_view, editable);
236 if (editable) {
237 lock_text (ui);
242 static void
243 close_review (GtkWidget *button, gpointer data)
245 GtkWidget *review_dialog = GTK_WIDGET (data);
247 gtk_widget_hide (review_dialog);
250 static gboolean
251 delete_review (GtkWidget *widget, GdkEvent *event, gpointer user_data)
253 gtk_widget_hide (widget);
255 return TRUE; /* don't destroy */
258 static void
259 show_review (GtkWidget *button, gpointer data)
261 GtkWidget *review_dialog, *main_window;
262 GtkWidget *edit, *copy, *close;
263 GtkBuilder *ui = (GtkBuilder*) data;
264 static gboolean initialized = FALSE;
266 if (!initialized) {
267 review_dialog = GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog"));
268 main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window"));
269 copy = GTK_WIDGET (gtk_builder_get_object (ui, "copy-review-button"));
270 edit = GTK_WIDGET (gtk_builder_get_object (ui, "edit-review-button"));
271 close = GTK_WIDGET (gtk_builder_get_object (ui, "close-review-button"));
273 gtk_window_set_transient_for (GTK_WINDOW (review_dialog), GTK_WINDOW (main_window));
275 g_signal_connect (G_OBJECT (copy), "clicked", G_CALLBACK (copy_review), ui);
276 g_signal_connect (G_OBJECT (edit), "toggled", G_CALLBACK (edit_review), ui);
277 g_signal_connect (G_OBJECT (close), "clicked", G_CALLBACK (close_review), review_dialog);
278 g_signal_connect (G_OBJECT (review_dialog), "delete-event", G_CALLBACK (delete_review), NULL);
280 initialized = TRUE;
283 lock_text (ui);
285 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog")));
289 static GnomeVersionInfo*
290 get_gnome_version_info (void)
292 GnomeVersionInfo *version;
293 xmlDoc *doc;
294 char *xml_file;
295 xmlNode *node;
296 guchar *platform, *minor, *micro, *distributor, *date;
298 version = g_new0 (GnomeVersionInfo, 1);
300 xml_file = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_DATADIR,
301 "gnome-about/gnome-version.xml",
302 TRUE, NULL);
303 if (!xml_file)
304 return NULL;
305 doc = xmlParseFile (xml_file);
306 g_free (xml_file);
308 if (!doc)
309 return NULL;
311 platform = minor = micro = distributor = date = NULL;
313 for (node = xmlDocGetRootElement (doc)->children; node; node = node->next) {
314 if (!strcmp ((char *)node->name, "platform"))
315 platform = xmlNodeGetContent (node);
316 else if (!strcmp ((char *)node->name, "minor"))
317 minor = xmlNodeGetContent (node);
318 else if (!strcmp ((char *)node->name, "micro"))
319 micro = xmlNodeGetContent (node);
320 else if (!strcmp ((char *)node->name, "distributor"))
321 distributor = xmlNodeGetContent (node);
322 else if (!strcmp ((char *)node->name, "date"))
323 date = xmlNodeGetContent (node);
326 if (platform && minor && micro)
327 version->gnome_platform = g_strdup_printf ("%s.%s.%s", platform, minor, micro);
329 if (distributor && *distributor)
330 version->gnome_distributor = g_strdup ((char *)distributor);
332 if (date && *date)
333 version->gnome_date = g_strdup ((char *)date);
335 xmlFree (platform);
336 xmlFree (minor);
337 xmlFree (micro);
338 xmlFree (distributor);
339 xmlFree (date);
341 xmlFreeDoc (doc);
343 return version;
346 static gboolean
347 update_progress_bar (gpointer data)
349 GtkProgressBar *pbar = GTK_PROGRESS_BAR (data);
351 gtk_progress_bar_pulse (pbar);
353 return TRUE;
356 static void
357 save_email (const char *email)
359 GConfClient *conf_client;
361 conf_client = gconf_client_get_default ();
362 gconf_client_set_string (conf_client, "/apps/bug-buddy/email_address", email, NULL);
363 g_object_unref (conf_client);
366 static void
367 link_callback (GtkLinkButton *button, gpointer user_data)
369 const gchar *link = gtk_link_button_get_uri (button);
371 if (gnome_vfs_url_show (link) != GNOME_VFS_OK) {
372 char *text;
374 text = g_markup_printf_escaped (_("Bug Buddy was unable to view the link \"%s\"\n"), link);
375 buddy_error (NULL, text);
376 g_free (text);
379 return;
382 static void
383 save_to_file (const gchar *filename, const gchar *text)
385 GError *error = NULL;
387 if (!g_file_set_contents (filename, text, -1, &error)) {
388 g_warning ("Unable to save document %s: %s\n", filename, error->message);
389 g_error_free (error);
394 static void
395 network_error (SoupMessage *msg, GtkBuilder *ui)
398 GtkWidget *dialog;
399 int res;
401 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
403 dialog = gtk_message_dialog_new_with_markup (NULL,
404 GTK_DIALOG_MODAL,
405 GTK_MESSAGE_QUESTION,
406 GTK_BUTTONS_YES_NO,
407 _("<span weight=\"bold\">Network Connection Error</span>\n"
408 "Maybe no Network Connection available.\n"
409 "Do you want to store this report until "
410 "a Network Connection is available?"));
411 res = gtk_dialog_run (GTK_DIALOG (dialog));
412 gtk_widget_destroy (dialog);
414 if (res == GTK_RESPONSE_YES) {
415 gchar *dirname;
416 gchar *filename;
417 xmlChar *message_string;
419 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
420 if (!g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
421 g_mkdir_with_parents (dirname, 0755);
424 filename = g_strdup_printf ("%s/%ld", dirname, (long)time (NULL));
426 message_string = soup_xmlrpc_message_to_string (SOUP_XMLRPC_MESSAGE (msg));
428 save_to_file (filename, (const gchar*) message_string);
430 xmlFree (message_string);
431 g_free (dirname);
432 g_free (filename);
435 bug_buddy_quit (ui);
436 return;
439 static void
440 remove_pending_reports (void)
442 GDir *dir;
443 char *dirname;
444 GError *error = NULL;
446 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
447 dir = g_dir_open (dirname, 0, &error);
448 if (dir) {
449 const char *name = g_dir_read_name (dir);
450 while (name) {
451 char *path = g_strdup_printf ("%s/%s", dirname, name);
452 g_remove (path);
453 g_free (path);
454 name = g_dir_read_name (dir);
456 g_dir_close (dir);
459 g_remove (dirname);
460 g_free (dirname);
463 static void
464 all_sent (GtkBuilder *ui)
466 GtkWidget *close_button;
468 /* hide the progressbar */
469 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
471 close_button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
472 gtk_widget_show (close_button);
475 static void
476 previous_sent (SoupMessage *msg, GtkBuilder *ui)
478 if (--bug_count == 0) {
479 all_sent (ui);
484 static void
485 bug_sent (SoupMessage *msg, GtkBuilder *ui)
487 GtkWidget *button;
488 GtkWidget *image;
489 char *text = NULL;
490 char *errmsg = NULL;
491 char *str = NULL;
492 char *ptr = NULL;
493 long bugid;
494 GtkWidget *urlbutton;
495 GtkRequisition requisition;
496 GError *err = NULL;
498 button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
499 gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
500 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
502 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON),
503 gtk_button_set_image (GTK_BUTTON (button), image);
505 if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
506 network_error (msg, ui);
507 } else {
508 remove_pending_reports ();
511 /* parse the XML-RPC response from Bugzilla */
512 bugid = bugzilla_parse_response (msg, &err);
514 if (bugid > 0) {
515 GtkWidget *main_vbox;
517 /* we need a reference to the vbox containing the text so that we
518 * can add a GtkLinkButton to the bug report */
519 main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox"));
521 text = g_strdup_printf ("http://bugzilla.gnome.org/show_bug.cgi?id=%ld", bugid);
523 /* create a clickable link to the bug report */
524 urlbutton = gtk_link_button_new (text);
525 g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL);
526 gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0);
528 gtk_widget_show (urlbutton);
529 g_free (text);
531 text = g_markup_printf_escaped (_("A bug report detailing your software crash has been sent to GNOME. "
532 "This information will allow the developers to understand the cause "
533 "of the crash and prepare a solution for it.\n\n"
534 "You may be contacted by a GNOME developer if more details are "
535 "required about the crash.\n\n"
536 "You can view your bug report and follow its progress with this URL:\n")) ;
538 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), text);
539 g_free (text);
540 save_email (gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry"))));
541 } else {
542 errmsg = _("Bug Buddy has encountered an error while submitting your report "
543 "to the Bugzilla server. Details of the error are included below.\n\n");
545 if (err != NULL) {
546 switch (err->code) {
547 case BUGZILLA_ERROR_RECV_BAD_STATUS:
548 text = g_strdup_printf (_("Server returned bad state. This is most likely a server "
549 "issue and should be reported to bugmaster@gnome.org\n\n%s"),
550 err->message);
551 break;
552 case BUGZILLA_ERROR_RECV_PARSE_FAILED:
553 text = g_strdup_printf (_("Failed to parse the xml-rpc response. Response follows:\n\n%s"),
554 err->message);
555 break;
556 case BUGZILLA_ERROR_RECV_FAULT:
557 /* in this case, the error message returned is the faultCode and faultString from
558 * the XML-RPC response, separated by a colon. We construct our error message
559 * based on the faultString */
560 ptr = strstr (err->message, ":");
561 if (ptr == NULL) {
562 text = g_strdup_printf (_("Bugzilla reported an error when trying to process your "
563 "request, but was unable to parse the response."));
564 break;
567 /* skip the colon */
568 ptr++;
570 /* see http://cvs.gnome.org/viewcvs/bugzilla-newer/Bugzilla/RPC.pm?view=markup */
571 if (g_str_equal (ptr, "invalid_username")) {
572 text = g_strdup_printf (_("The email address you provided is not valid."));
573 } else if (g_str_equal (ptr, "account_disabled")) {
574 text = g_strdup_printf (_("The account associated with the email address "
575 "provided has been disabled."));
576 } else if (g_str_equal (ptr, "product_doesnt_exist")) {
577 text = g_strdup_printf (_("The product specified doesn't exist or has been "
578 "renamed. Please upgrade to the latest version."));
579 } else if (g_str_equal (ptr, "component_not_valid")) {
580 text = g_strdup_printf (_("The component specified doesn't exist or has been "
581 "renamed. Please upgrade to the latest version."));
582 } else if (g_str_equal (ptr, "require_summary")) {
583 text = g_strdup_printf (_("The summary is required in your bug report. "
584 "This should not happen with the latest Bug Buddy."));
585 } else if (g_str_equal (ptr, "description_required")) {
586 text = g_strdup_printf (_("The description is required in your bug report. "
587 "This should not happen with the latest Bug Buddy."));
588 } else {
589 text = g_strdup_printf (_("The fault code returned by Bugzilla is not recognized. "
590 "Please report the following information to "
591 "bugzilla.gnome.org manually:\n\n%s"), err->message);
594 break;
595 default:
596 text = g_strdup_printf (_("An unknown error occurred. This is most likely a problem with "
597 "bug-buddy. Please report this problem manually at bugzilla."
598 "gnome.org\n\n"));
599 break;
602 str = g_strconcat (errmsg, text, NULL);
603 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), str);
605 g_free (str);
606 g_free (text);
607 g_error_free (err);
611 if (--bug_count == 0) {
612 all_sent (ui);
615 gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui, "main-window")), &requisition);
616 gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
617 requisition.width, requisition.height);
622 static void
623 set_proxy (SoupSession *session)
625 GConfClient *gconf_client;
626 char *host;
627 int port;
628 char *proxy_uri;
629 SoupUri *uri;
630 char *username = NULL;
631 char *password = NULL;
634 gconf_client = gconf_client_get_default ();
636 if (gconf_client_get_bool (gconf_client, USE_PROXY_KEY, NULL) == FALSE) {
637 g_object_unref (gconf_client);
638 return;
641 host = gconf_client_get_string (gconf_client, PROXY_HOST_KEY, NULL);
642 if (host == NULL) {
643 g_object_unref (gconf_client);
644 return;
646 port = gconf_client_get_int (gconf_client, PROXY_PORT_KEY, NULL);
647 if (port == 0)
648 port = 80;
650 if (gconf_client_get_bool (gconf_client, USE_PROXY_AUTH, NULL)) {
651 username = gconf_client_get_string (gconf_client, PROXY_USER, NULL);
652 password = gconf_client_get_string (gconf_client, PROXY_PASSWORD, NULL);
655 if (username && password)
656 proxy_uri = g_strdup_printf ("http://%s:%s@%s:%d", username, password, host, port);
657 else
658 proxy_uri = g_strdup_printf ("http://%s:%d", host, port);
660 uri = soup_uri_new (proxy_uri);
661 g_object_set (G_OBJECT (session), "proxy-uri", uri, NULL);
663 g_free (host);
664 g_free (username);
665 g_free (password);
666 g_free (proxy_uri);
667 soup_uri_free (uri);
668 g_object_unref (gconf_client);
673 static char*
674 create_report_title (BugzillaApplication *app, int type, const char *description)
676 char *title;
677 long size = 0;
678 char *tmp = NULL;
680 if (description) {
681 tmp = g_malloc0 (256); /* This should be safe enough for 24 UTF-8 chars.
682 * anyway, I miss a g_utf8_strndup :) */
683 size = g_utf8_strlen (description, -1);
684 if (size > 24) {
685 g_utf8_strncpy (tmp, description, 24);
686 } else {
687 g_utf8_strncpy (tmp, description, size);
691 if (type == BUG_TYPE_CRASH) {
692 title = g_strdup_printf ("crash in %s: %s%s", app->cname,
693 tmp ? tmp : "empty description",
694 (tmp && size > 24) ? "..." : "");
695 } else {
696 title = g_strdup_printf ("%s: %s%s", app->cname,
697 tmp ? tmp : "empty description",
698 (tmp && size > 24) ? "..." : "");
701 g_free (tmp);
703 return title;
705 static void
706 send_report (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui)
708 GtkTextView *text_view;
709 GtkTextBuffer *buffer;
710 GtkTextIter start;
711 GtkTextIter end;
712 int type;
713 char *gdb_text;
714 char *details_text;
715 char *title;
716 char *final_text;
717 const char *email;
718 SoupSession *session;
719 SoupXmlrpcMessage *message;
720 GError *err = NULL;
722 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check")));
724 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
725 buffer = gtk_text_view_get_buffer (text_view);
726 gtk_text_buffer_get_start_iter (buffer, &start);
727 gtk_text_buffer_get_end_iter (buffer, &end);
728 gdb_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
730 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "details-view"));
731 buffer = gtk_text_view_get_buffer (text_view);
732 gtk_text_buffer_get_start_iter (buffer, &start);
733 gtk_text_buffer_get_end_iter (buffer, &end);
734 details_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
736 type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(ui), "type"));
737 final_text = g_strdup_printf ("%s%s\n\n\n%s",
738 type == BUG_TYPE_CRASH ? "What were you doing when the application crashed?\n" : "",
739 details_text != NULL ? details_text : "",
740 gdb_text != NULL ? gdb_text : "<empty backtrace>");
742 email = gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry")));
743 title = create_report_title (app, type, details_text);
745 message = bugzilla_create_report (app, type, gnome_version, email, title, final_text, ui, &err);
746 if (message == NULL) {
747 char *text;
749 if (err != NULL) {
750 text = g_strdup_printf (_("Unable to create the bug report: %s\n"), err->message);
751 } else {
752 text = g_strdup_printf (_("There was an error creating the bug report\n"));
755 buddy_error (NULL, text);
756 g_free (text);
757 g_free (gdb_text);
758 g_free (details_text);
759 g_free (title);
760 g_free (final_text);
761 bug_buddy_quit (ui);
762 return;
765 session = soup_session_async_new ();
766 set_proxy (session);
769 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ui, "pending-reports-check")))) {
770 GDir *dir;
771 char *dirname;
772 GError *error = NULL;
774 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
775 dir = g_dir_open (dirname, 0, &error);
776 if (dir) {
777 const char *name = g_dir_read_name (dir);
778 while (name != NULL) {
779 char *path;
780 char *contents;
782 path = g_strdup_printf ("%s/%s", dirname, name);
783 if (g_file_get_contents (path, &contents, NULL, NULL)) {
784 SoupXmlrpcMessage *msg;
785 msg = soup_xmlrpc_message_new ("http://bugzilla.gnome.org/bugbuddy.cgi");
786 soup_xmlrpc_message_from_string (msg, contents);
787 bug_count++;
788 soup_xmlrpc_message_persist (msg);
789 soup_session_queue_message (session, SOUP_MESSAGE (msg),
790 (SoupMessageCallbackFn)previous_sent, ui);
791 g_free (contents);
793 g_free (path);
794 name = g_dir_read_name (dir);
797 g_dir_close (dir);
801 bug_count++;
803 soup_xmlrpc_message_persist (message);
804 soup_session_queue_message (session, SOUP_MESSAGE (message),
805 (SoupMessageCallbackFn)bug_sent, ui);
806 g_free (gdb_text);
807 g_free (details_text);
808 g_free (title);
809 g_free (final_text);
811 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
812 _("Sending..."));
816 /* A local part is valid if it is one or more valid characters. */
817 static gboolean
818 email_local_part_is_valid (const char *local_part)
820 const char *character;
822 if (!local_part[0])
823 return FALSE;
825 for (character = local_part; *character; character++) {
826 /* If character is alphanumeric it is valid. */
827 if (g_ascii_isalnum (*character))
828 continue;
830 /* If character is "-", "_" or "." it is valid. */
831 if (*character == '-' || *character == '_' || *character == '.')
832 continue;
834 /* Not valid character, not valid local part. */
835 return FALSE;
838 return TRUE;
841 /* A domain label is valid if it is one or more valid characters. */
842 static gboolean
843 email_domain_label_is_valid (const char *domain_label)
845 const char *character;
846 int i;
848 /* Validate each character, whilst measuring length, i. */
849 for (i = 0; *(character = domain_label + i); i++) {
851 /* If character is alphanumeric it is valid. */
852 if (g_ascii_isalnum (*character))
853 continue;
855 /* If it's a hyphen, it's also valid. */
856 if (*character == '-')
857 continue;
859 /* Anything else is invalid */
860 return FALSE;
863 /* Labels must be between 1 and 63 characters long */
864 if (i < 1 || i > 63) {
865 return FALSE;
868 return TRUE;
871 /* A domain is valid if it is one or more valid, dot-separated labels. */
872 static gboolean
873 email_domain_is_valid (const char *domain)
875 char **labels;
876 char **this_label;
878 /* If there is no domain, there are no domain labels and the domain is
879 * not valid. */
880 if (!domain[0])
881 return FALSE;
883 /* Split the domain on the dot to validate labels. */
884 labels = g_strsplit (domain, ".", 0);
886 for (this_label = labels; *this_label; this_label++) {
887 if (!email_domain_label_is_valid (*this_label)) {
888 g_strfreev (labels);
889 return FALSE;
893 g_strfreev (labels);
894 return TRUE;
897 /* Check for *simple* email addresses of the form user@host, with checks
898 * in characters used, and sanity checks on the form of host.
900 /* FIXME: Should we provide a useful error message? */
901 static gboolean
902 email_is_valid (const char *address)
904 char *local_part;
905 char *domain;
906 char **parts;
907 gboolean is_valid;
909 parts = g_strsplit (address, "@", 2);
911 /* Check we have the 2 parts */
912 if (!(local_part = parts[0]) || !(domain = parts[1])) {
913 g_strfreev (parts);
914 return FALSE;
917 is_valid = email_local_part_is_valid (local_part)
918 && email_domain_is_valid (domain);
920 g_strfreev (parts);
921 return is_valid;
924 static void
925 check_email (GtkEditable *editable, gpointer data)
927 const char *email;
928 GtkBuilder *ui = (GtkBuilder*) data;
930 email = gtk_entry_get_text (GTK_ENTRY (editable));
931 gtk_widget_set_sensitive (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")),
932 email_is_valid (email));
936 static void
937 on_send_clicked (GtkWidget *button, gpointer data)
939 BugzillaApplication *app;
940 GnomeVersionInfo *gnome_version;
941 GtkRequisition requisition;
942 GtkBuilder *ui = (GtkBuilder*) data;
944 app = g_object_get_data (G_OBJECT (ui), "app");
945 gnome_version = g_object_get_data (G_OBJECT (ui), "gnome-version");
947 /* hide the send button immediately so that the user can't click
948 * it more than once (this will create multiple bugs) */
949 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
951 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
952 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
953 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "review-box")));
955 gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui, "main-window")), &requisition);
956 gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
957 requisition.width, requisition.height);
959 send_report (app, gnome_version, ui);
962 static gboolean
963 gdb_insert_text (const gchar *stacktrace, GtkBuilder *ui)
965 GtkTextView *text_view;
966 GtkTextIter end;
967 GtkTextBuffer *buffer;
969 /* FIXME: These strings are gdb specific, we should add here also dbx */
970 const char *bt_step1 = "#1";
971 const char *bt_step2 = "#2";
972 const char *bt_step3 = "#3";
974 if (!g_strrstr (stacktrace, bt_step1) &&
975 !g_strrstr (stacktrace, bt_step2) &&
976 !g_strrstr (stacktrace, bt_step3)) {
977 return FALSE;
982 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
983 buffer = gtk_text_view_get_buffer (text_view);
984 gtk_text_buffer_get_end_iter (buffer, &end);
986 /* add the stacktrace to the GtkTextView */
987 gtk_text_buffer_insert (buffer, &end, stacktrace, strlen (stacktrace));
989 return TRUE;
992 static void
993 show_pending_checkbox_if_pending (GtkBuilder *ui)
995 char *dirname;
996 GtkWidget *check;
998 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
999 if (g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
1000 check = GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check"));
1001 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE);
1002 gtk_widget_show (check);
1004 g_free (dirname);
1007 static GtkWidget*
1008 create_debuginfo_link (void)
1010 GtkWidget *urlbutton;
1012 /* create a clickable link to the bug report */
1013 urlbutton = gtk_link_button_new_with_label ("http://live.gnome.org/GettingTraces/DistroSpecificInstructions",
1014 /* Translators: This is the hyperlink which takes to http://live.gnome.org/GettingTraces/DistroSpecificInstructions
1015 * page. Please also mention that the page is in English */
1016 _("Getting useful crash reports"));
1017 g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL);
1019 return urlbutton;
1023 static void
1024 useless_finished (GtkBuilder *ui)
1027 GtkWidget *button, *image, *main_vbox, *urlbutton;
1028 BugzillaApplication *app;
1029 char *label_text;
1031 app = g_object_get_data (G_OBJECT (ui), "app");
1033 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1035 label_text = g_markup_printf_escaped (_("The application %s crashed. The bug reporting tool was "
1036 "unable to collect enough information about the crash to be "
1037 "useful to the developers.\n\n"
1038 "In order to submit useful reports, please consider installing "
1039 "debug packages for your distribution.\n"
1040 "Click the link below to get information about how to install "
1041 "these packages:\n"),
1042 app->name);
1043 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
1044 label_text);
1046 main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox"));
1047 urlbutton = create_debuginfo_link ();
1048 gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0);
1050 gtk_widget_show (urlbutton);
1052 g_free (label_text);
1054 button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
1055 gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
1056 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
1058 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON),
1059 gtk_button_set_image (GTK_BUTTON (button), image);
1061 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "email-entry")));
1065 static void
1066 known_app_finished (GtkBuilder *ui)
1068 BugzillaApplication *app;
1069 GtkWidget *email_entry;
1070 GtkWidget *button;
1071 char *default_email;
1072 char *lang_note, *label_text, *s;
1073 const char *en_lang_note = N_("\n\nPlease write your report in English, if possible.");
1075 app = g_object_get_data (G_OBJECT (ui), "app");
1077 fill_custom_info (app, ui);
1078 fill_stderr_info (ui);
1080 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1081 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1082 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1084 lang_note = gettext (en_lang_note);
1086 label_text = g_strconcat (_("Information about the %s application crash has been successfully collected. "
1087 "Please provide some more details about what you were doing when "
1088 "the application crashed.\n\n"
1090 "A valid email address is required. This will allow the developers to "
1091 "contact you for more information if necessary."),
1092 strcmp (lang_note, en_lang_note) ? lang_note : NULL,
1093 NULL);
1095 s = g_markup_printf_escaped (label_text, app->name);
1097 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
1100 g_free (s);
1101 g_free (label_text);
1103 show_pending_checkbox_if_pending (ui);
1105 button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button"));
1106 g_signal_connect (button, "clicked",
1107 G_CALLBACK (on_send_clicked), ui);
1109 email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"));
1110 g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui);
1112 default_email = get_default_user_email ();
1114 if (default_email != NULL) {
1115 gtk_entry_set_text (GTK_ENTRY (email_entry), default_email);
1116 g_free (default_email);
1117 } else {
1118 gtk_widget_set_sensitive (button, FALSE);
1121 if (search_forbidden_words (ui)) {
1122 char *review_text = g_markup_printf_escaped ("<small><i><span weight=\"bold\">%s</span> %s</i></small>",
1123 _("WARNING:"),
1124 _("Some sensitive data is likely present in the crash details. "
1125 "Please review and edit the information if you are concerned "
1126 "about transmitting passwords or other sensitive data."));
1127 gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "review-label")), review_text);
1129 g_signal_connect (gtk_builder_get_object (ui, "review-button"), "clicked", G_CALLBACK (show_review), ui);
1130 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-box")));
1132 gtk_widget_grab_focus (GTK_WIDGET (gtk_builder_get_object (ui, "details-view")));
1135 static void
1136 gdb_finished (const gchar *stacktrace, gpointer data)
1138 GtkBuilder *ui = (GtkBuilder*) data;
1140 if (gdb_insert_text (stacktrace, ui)) {
1141 known_app_finished (ui);
1142 } else {
1143 useless_finished (ui);
1149 static void
1150 on_save_clicked (GtkWidget *button, gpointer user_data)
1152 GtkBuilder *ui = (GtkBuilder *)user_data;
1153 GtkWidget *dialog;
1154 const char *desktop;
1155 char *filename;
1156 gboolean desktop_is_home_dir, saved;
1157 GConfClient *gconf_client;
1159 saved = FALSE;
1161 dialog = gtk_file_chooser_dialog_new (_("Save File"),
1162 GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
1163 GTK_FILE_CHOOSER_ACTION_SAVE,
1164 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1165 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1166 NULL);
1168 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
1170 gconf_client = gconf_client_get_default ();
1171 desktop_is_home_dir = gconf_client_get_bool (gconf_client, DESKTOP_IS_HOME_DIR, NULL);
1172 g_object_unref (gconf_client);
1174 if (desktop_is_home_dir)
1175 desktop = g_get_home_dir();
1176 else
1177 desktop = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
1179 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), desktop);
1181 filename = g_strconcat (gopt_data.app_file, _("-bugreport.txt"), NULL);
1182 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
1183 g_free (filename);
1185 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
1186 char *filename;
1187 GtkTextView *text_view;
1188 GtkTextBuffer *buffer;
1189 GtkTextIter start;
1190 GtkTextIter end;
1191 gchar *text;
1193 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1194 buffer = gtk_text_view_get_buffer (text_view);
1195 gtk_text_buffer_get_start_iter (buffer, &start);
1196 gtk_text_buffer_get_end_iter (buffer, &end);
1197 text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1199 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1200 save_to_file (filename, text);
1201 g_free (filename);
1202 g_free (text);
1203 saved = TRUE;
1206 gtk_widget_destroy (dialog);
1207 if (saved) {
1208 bug_buddy_quit (ui);
1212 static void
1213 focus_details (GtkWidget *widget, gpointer data)
1215 gtk_widget_grab_focus (widget);
1218 static void
1219 unknown_app_finished (GtkBuilder *ui)
1221 GtkWidget *button;
1222 char *label_text;
1224 fill_stderr_info (ui);
1226 /* don't need user input, so hide these widgets */
1227 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1228 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1230 /* make the send button into a save button :-) */
1231 button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button"));
1232 gtk_button_set_label (GTK_BUTTON (button), _("_Save Bug Report"));
1233 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
1234 g_signal_connect (GTK_BUTTON (button), "clicked", G_CALLBACK (on_save_clicked), ui);
1235 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1237 label_text = g_markup_printf_escaped (_("The application %s has crashed.\n"
1238 "Information about the crash has been successfully collected.\n\n"
1239 "This application is not known to bug-buddy, therefore the "
1240 "bug report cannot be sent to the GNOME Bugzilla. Please save the "
1241 "bug to a text file and report it to the appropriate bug tracker "
1242 "for this application."), gopt_data.app_file);
1243 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), label_text);
1245 /* FIXME: If we just grab the focus here to the GtkTextView it will crash on the blink_cb because
1246 * the window is nop mapped! Is this a gtk+ bug? are we doing something wrong?
1247 * Let's do a funny Workaround: */
1248 g_signal_connect_after (gtk_builder_get_object (ui, "details-view"), "realize", G_CALLBACK (focus_details), NULL);
1249 gtk_widget_realize (GTK_WIDGET (gtk_builder_get_object (ui, "details-view")));
1253 static void
1254 send_minidump (GtkBuilder *ui)
1256 /* FIXME: Implement the MiniDump sending code */
1257 GtkWidget *button;
1259 button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
1260 gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
1261 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
1263 /* don't need user input, so hide these widgets */
1264 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1265 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1266 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1268 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
1269 _("Your application has crashed. Information about the crash has been successfully collected.\n\n"
1270 "However we are working on GNOME debug server to handle correctly this information.\n\n"));
1275 static void
1276 gdb_finished_unknown_app (const gchar *stacktrace, gpointer data)
1278 GtkBuilder *ui = (GtkBuilder*) data;
1280 gdb_insert_text (stacktrace, ui);
1281 unknown_app_finished (ui);
1284 static void
1285 bug_buddy_quit (GtkBuilder *ui)
1287 gpointer data;
1289 g_return_if_fail (ui != NULL);
1291 data = g_object_get_data (G_OBJECT (ui), "sourceid");
1293 if (data != NULL) {
1294 guint source_id = GPOINTER_TO_UINT (data);
1296 /* removes the context from the main loop and kills any remaining
1297 * gdb process */
1298 if (source_id > 0) {
1299 g_source_remove (source_id);
1300 g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (0));
1304 g_hash_table_destroy (apps);
1306 g_object_unref (ui);
1308 gtk_main_quit ();
1311 static void
1312 close_callback (GtkWidget *widget, gpointer user_data)
1314 GtkBuilder *ui = (GtkBuilder *)user_data;
1316 bug_buddy_quit (ui);
1319 static void
1320 help_callback (GtkWidget *widget, gpointer user_data)
1322 GnomeProgram *program = (GnomeProgram *)user_data;
1323 GError *error = NULL;
1325 gnome_help_display_desktop (program,
1326 "user-guide",
1327 "user-guide",
1328 "feedback-bugs",
1329 &error);
1331 if (error) {
1332 GtkWidget *error_dialog =
1333 gtk_message_dialog_new (NULL,
1334 GTK_DIALOG_MODAL,
1335 GTK_MESSAGE_ERROR,
1336 GTK_BUTTONS_CLOSE,
1337 _("There was an error displaying help: %s"),
1338 error->message);
1340 g_signal_connect (G_OBJECT (error_dialog), "response",
1341 G_CALLBACK (gtk_widget_destroy), NULL);
1343 gtk_window_set_resizable (GTK_WINDOW (error_dialog), FALSE);
1345 gtk_widget_show (error_dialog);
1346 g_error_free (error);
1347 error = NULL;
1351 static gboolean
1352 delete_callback (GtkWidget *widget, GdkEvent *event, gpointer data)
1354 close_callback (NULL, data);
1355 return TRUE;
1358 static void
1359 fill_gnome_info (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui)
1361 char *version_info;
1362 char *distro;
1363 GtkTextView *text_view;
1364 GtkTextIter end;
1365 GtkTextBuffer *buffer;
1367 g_return_if_fail (app != NULL);
1368 g_return_if_fail (gnome_version != NULL);
1369 g_return_if_fail (ui != NULL);
1371 distro = get_distro_name ();
1372 version_info = g_strdup_printf ("Distribution: %s\n"
1373 "Gnome Release: %s %s (%s)\n"
1374 "BugBuddy Version: %s\n"
1375 "\n",
1376 distro,
1377 gnome_version->gnome_platform, gnome_version->gnome_date,
1378 gnome_version->gnome_distributor, VERSION);
1380 g_free (distro);
1382 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1383 buffer = gtk_text_view_get_buffer (text_view);
1384 gtk_text_buffer_get_end_iter (buffer, &end);
1385 gtk_text_buffer_insert (buffer, &end, version_info, strlen (version_info));
1387 g_free (version_info);
1390 static void
1391 fill_custom_info (BugzillaApplication *app, GtkBuilder *ui)
1393 GtkTextView *text_view;
1394 GtkTextIter end;
1395 GtkTextBuffer *buffer;
1396 gint status;
1397 gchar *output;
1398 gchar *standard_output = NULL;
1399 gchar *standard_error = NULL;
1400 GError *error = NULL;
1402 g_return_if_fail (app != NULL);
1403 g_return_if_fail (ui != NULL);
1405 if (app->extra_info_script == NULL) {
1406 return;
1409 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1410 buffer = gtk_text_view_get_buffer (text_view);
1411 gtk_text_buffer_get_end_iter (buffer, &end);
1413 if (!g_spawn_command_line_sync (app->extra_info_script, &standard_output, &standard_error,
1414 &status, &error)) {
1415 gchar *error_string = g_strdup_printf ("There was an error running \"%s\" script:\n"
1416 "%s", app->extra_info_script, error->message);
1417 gtk_text_buffer_insert (buffer, &end, error_string, strlen (error_string));
1419 g_free (error);
1420 g_free (error_string);
1421 return;
1424 output = g_strdup_printf ("Output of custom script \"%s\":\n"
1425 "%s\n\n",
1426 app->extra_info_script,
1427 standard_output ? standard_output : "");
1429 gtk_text_buffer_insert (buffer, &end, output, strlen (output));
1431 g_free (output);
1432 g_free (standard_output);
1433 g_free (standard_error);
1437 static void
1438 fill_proccess_info (pid_t pid, GtkBuilder *ui)
1440 GtkTextView *text_view;
1441 GtkTextIter end;
1442 GtkTextBuffer *buffer;
1443 char *mem;
1444 char *time;
1445 char *proccess_info;
1447 mem = proccess_get_mem_state (pid);
1448 time = proccess_get_time (pid);
1450 proccess_info = g_strdup_printf ("%s\n"
1451 "%s\n"
1452 "\n",
1453 mem, time);
1455 g_free (mem);
1456 g_free (time);
1458 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1459 buffer = gtk_text_view_get_buffer (text_view);
1460 gtk_text_buffer_get_end_iter (buffer, &end);
1461 gtk_text_buffer_insert (buffer, &end, proccess_info, strlen (proccess_info));
1463 g_free (proccess_info);
1467 static void
1468 fill_include_file (char *filename, GtkBuilder *ui)
1470 GtkTextView *text_view;
1471 GtkTextIter end;
1472 GtkTextBuffer *buffer;
1473 char *text;
1474 GError *error = NULL;
1476 if (g_file_get_contents (filename, &text, NULL, &error) != TRUE) {
1477 buddy_error (NULL, error->message);
1478 g_error_free (error);
1479 return;
1482 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1483 buffer = gtk_text_view_get_buffer (text_view);
1484 gtk_text_buffer_get_end_iter (buffer, &end);
1485 gtk_text_buffer_insert (buffer, &end, text, strlen (text));
1487 g_free (text);
1492 static void
1493 fill_system_info (GtkBuilder *ui)
1495 GConfClient *gconf_client;
1496 GtkTextView *text_view;
1497 GtkTextIter end;
1498 GtkTextBuffer *buffer;
1499 GString *system_info;
1500 struct utsname uts_buf;
1501 char *str;
1502 gboolean has_selinux, enforcing, a11y;
1504 g_return_if_fail (ui != NULL);
1506 system_info = g_string_new ("");
1508 if (uname (&uts_buf) == 0) {
1509 g_string_append_printf (system_info, "System: %s %s %s %s\n", uts_buf.sysname, uts_buf.release, uts_buf.version, uts_buf.machine);
1512 /* X server checks */
1513 g_string_append_printf (system_info, "X Vendor: %s\n", ServerVendor (gdk_display));
1514 g_string_append_printf (system_info, "X Vendor Release: %d\n", VendorRelease (gdk_display));
1517 /* Selinux checks */
1518 has_selinux = FALSE;
1519 if (g_file_get_contents ("/proc/filesystems", &str, NULL, NULL)) {
1520 has_selinux = strstr (str, "selinuxfs") != NULL;
1521 g_free (str);
1523 if (has_selinux) {
1524 enforcing = TRUE;
1525 if (g_file_get_contents ("/selinux/enforce", &str, NULL, NULL)) {
1526 enforcing = strcmp (str, "0") != 0;
1527 g_free (str);
1529 g_string_append_printf (system_info, "Selinux: %s\n", enforcing?"Enforcing":"Permissive");
1530 } else {
1531 g_string_append_printf (system_info, "Selinux: No\n");
1534 /* A11y and gtk */
1535 gconf_client = gconf_client_get_default ();
1536 a11y = gconf_client_get_bool (gconf_client, ACCESSIBILITY_KEY, NULL);
1537 g_string_append_printf (system_info, "Accessibility: %s\n", a11y?"Enabled":"Disabled");
1538 str = gconf_client_get_string (gconf_client, GTK_THEME_KEY, NULL);
1539 g_string_append_printf (system_info, "GTK+ Theme: %s\n", str);
1540 g_free (str);
1541 str = gconf_client_get_string (gconf_client, ICON_THEME_KEY, NULL);
1542 g_string_append_printf (system_info, "Icon Theme: %s\n", str);
1543 g_free (str);
1544 g_object_unref (gconf_client);
1546 g_string_append (system_info, "\n");
1548 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1549 buffer = gtk_text_view_get_buffer (text_view);
1550 gtk_text_buffer_get_end_iter (buffer, &end);
1551 gtk_text_buffer_insert (buffer, &end, system_info->str, system_info->len);
1553 g_string_free (system_info, TRUE);
1556 static void
1557 fill_stderr_info (GtkBuilder *ui)
1559 GtkTextView *text_view;
1560 GtkTextIter end;
1561 GtkTextBuffer *buffer;
1562 GString *stderr_info;
1563 char *str, *file;
1564 gchar **lines;
1565 int n_lines, i;
1567 g_return_if_fail (ui != NULL);
1569 stderr_info = g_string_new ("");
1571 /* .xsession-errors: read file */
1572 file = g_build_filename (g_get_home_dir (), ".xsession-errors", NULL);
1573 if (g_file_get_contents (file, &str, NULL, NULL)) {
1574 lines = g_strsplit (str, "\n", -1);
1575 g_free (str);
1576 n_lines = 0;
1577 while (lines[n_lines] != NULL) {
1578 n_lines++;
1581 if (n_lines > 0) {
1582 struct stat buf;
1583 char *mtime_age = NULL;
1584 time_t age = 0;
1587 if (stat (file, &buf) == 0) {
1588 age = time (NULL) - buf.st_mtime;
1589 if (age > 5) {
1590 mtime_age = g_strdup_printf (" (%d sec old)", (int) age);
1594 g_string_append_printf (stderr_info,
1595 "\n\n----------- .xsession-errors%s ---------------------\n",
1596 mtime_age?mtime_age:"");
1597 g_free (mtime_age);
1600 for (i = MAX (0, n_lines-16); i < n_lines; i++) {
1601 if (lines[i][0] != 0) {
1602 /* Limit line length to 200 chars to avoid excessive data */
1603 if (strlen (lines[i]) > 200) {
1604 lines[i][200] = 0;
1607 g_string_append_printf (stderr_info, "%s\n", lines[i]);
1610 if (n_lines > 0)
1611 g_string_append (stderr_info, "--------------------------------------------------\n");
1613 g_strfreev (lines);
1615 g_free (file);
1617 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1618 buffer = gtk_text_view_get_buffer (text_view);
1619 gtk_text_buffer_get_end_iter (buffer, &end);
1620 gtk_text_buffer_insert (buffer, &end, stderr_info->str, stderr_info->len);
1622 g_string_free (stderr_info, TRUE);
1626 static GdkPixbuf*
1627 load_icon (const char *icon)
1629 GdkPixbuf *pixbuf = NULL;
1631 if (g_path_is_absolute (icon)) {
1632 pixbuf = gdk_pixbuf_new_from_file_at_size (icon, 48, 48, NULL);
1634 if (pixbuf == NULL) {
1635 pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
1636 icon, 48, 0, NULL);
1638 if (pixbuf == NULL && strrchr (icon, '.') != NULL) {
1639 char *name;
1640 name = g_strndup (icon, strlen (icon) - strlen (strrchr (icon, '.')));
1641 pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (),
1642 name, 48, 0, NULL);
1643 g_free (name);
1646 if (pixbuf == NULL) {
1647 char *filename;
1648 filename = g_strdup_printf (BUDDY_ICONDIR"/pixmaps/%s", icon);
1649 pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 48, 48, NULL);
1650 g_free (filename);
1653 return pixbuf;
1658 main (int argc, char *argv[])
1660 gchar *s;
1661 BugzillaApplication *app;
1662 GnomeVersionInfo *gnome_version;
1663 guint progress;
1664 GtkWidget *main_window;
1665 GOptionContext *context;
1666 GnomeProgram *program;
1667 guint source_id;
1668 GError *err = NULL;
1669 GtkBuilder *ui = NULL;
1671 memset (&gopt_data, 0, sizeof (gopt_data));
1673 bindtextdomain (PACKAGE, GNOMELOCALEDIR);
1674 bind_textdomain_codeset (PACKAGE, "UTF-8");
1675 textdomain (PACKAGE);
1677 context = g_option_context_new (N_("\n\nBug Buddy is a utility that helps report debugging\n"
1678 "information to the GNOME Bugzilla when a program crashes."));
1680 g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
1682 g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
1684 program = gnome_program_init (PACKAGE, VERSION,
1685 LIBGNOMEUI_MODULE,
1686 argc, argv,
1687 GNOME_PARAM_GOPTION_CONTEXT, context,
1688 GNOME_PARAM_APP_DATADIR, BUDDY_DATADIR,
1689 GNOME_CLIENT_PARAM_SM_CONNECT, FALSE,
1690 NULL);
1692 g_set_application_name (_("Bug Buddy"));
1693 gtk_window_set_default_icon_name ("bug-buddy");
1696 s = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_APP_DATADIR,
1697 "bug-buddy.gtkbuilder", TRUE, NULL);
1698 if (s) {
1699 ui = gtk_builder_new ();
1700 gtk_builder_add_from_file (ui, s, &err);
1701 gtk_builder_set_translation_domain (ui, GETTEXT_PACKAGE);
1704 if (!ui) {
1705 buddy_error (NULL,
1706 _("Bug Buddy could not load its user interface file.\n"
1707 "Please make sure Bug Buddy was installed correctly."));
1708 g_object_unref (program);
1709 return 0;
1711 g_free (s);
1713 main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window"));
1714 g_signal_connect (main_window, "delete-event", G_CALLBACK (delete_callback), ui);
1716 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1718 progress = g_timeout_add (100, update_progress_bar,
1719 gtk_builder_get_object (ui, "progressbar"));
1720 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
1721 _("Collecting information from your system..."));
1723 if (gopt_data.app_file == NULL && gopt_data.package == NULL) {
1724 buddy_error (NULL, _("Either --appname or --package arguments are required.\n"));
1725 g_object_unref (program);
1726 return 0;
1729 if (gopt_data.app_file && gopt_data.pid == 0 &&
1730 gopt_data.include_file == NULL &&
1731 gopt_data.minidump_file == NULL) {
1732 buddy_error (NULL, _("Either --pid , --include or --minidump arguments are required.\n"));
1733 g_object_unref (program);
1734 return 0;
1737 /* get some information about the gnome version */
1738 gnome_version = get_gnome_version_info ();
1739 if (gnome_version == NULL) {
1740 buddy_error (NULL, _("Bug Buddy was unable to retrieve information regarding "
1741 "the version of GNOME you are running. This is most likely "
1742 "due to a missing installation of gnome-desktop.\n"));
1743 g_object_unref (program);
1744 return 0;
1747 g_object_set_data (G_OBJECT (ui), "gnome-version", gnome_version);
1749 /* connect the signal handler for the help button */
1750 g_signal_connect (gtk_builder_get_object (ui, "help-button"), "clicked",
1751 G_CALLBACK (help_callback), program);
1753 gtk_widget_show (main_window);
1755 apps = load_applications ();
1757 /* If we have a binary file it is a crash */
1758 if (gopt_data.app_file) {
1759 app = g_hash_table_lookup (apps, gopt_data.app_file);
1761 /* we handle an unknown application (no .desktop file) differently */
1762 if (app != NULL) {
1763 s = g_markup_printf_escaped (_("The %s application has crashed. "
1764 "We are collecting information about the crash to send to the "
1765 "developers in order to fix the problem."), app->name);
1766 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s);
1767 g_free (s);
1769 g_object_set_data (G_OBJECT (ui), "app", app);
1771 if (app->icon) {
1772 GdkPixbuf *pixbuf;
1773 pixbuf = load_icon (app->icon);
1774 if (pixbuf) {
1775 gtk_image_set_from_pixbuf (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")),
1776 pixbuf);
1777 g_object_unref(pixbuf);
1780 fill_gnome_info (app, gnome_version, ui);
1783 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
1784 _("Collecting information from the crash..."));
1786 fill_system_info (ui);
1788 fill_proccess_info (gopt_data.pid, ui);
1790 if (gopt_data.pid > 0) {
1791 /* again, if this is an unknown application, we connect a different callback that
1792 * will allow the user to save the trace rather than sending it to the GNOME Bugzilla */
1793 if (app == NULL) {
1794 source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui,
1795 gdb_finished_unknown_app, &err);
1796 } else {
1797 source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui, gdb_finished, &err);
1800 if (source_id == 0) {
1801 buddy_error (NULL, _("Bug Buddy encountered the following error when trying "
1802 "to retrieve debugging information: %s\n"), err->message);
1803 g_error_free (err);
1804 g_object_unref (program);
1805 return 0;
1808 /* connect the close button callback so that we can remove the source from
1809 * the main loop (and kill gdb) if the user wants to quit before gdb is finished */
1810 g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (source_id));
1811 } else {
1812 if (gopt_data.minidump_file != NULL) {
1813 send_minidump (ui);
1814 } else if (app == NULL) {
1815 unknown_app_finished (ui);
1816 } else {
1817 known_app_finished (ui);
1820 g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_CRASH));
1821 } else {
1822 /* No binary file, so this is a non-crashing bug. Look the application from the --package arg */
1823 GtkWidget *email_entry;
1824 char *default_email;
1825 char *msg, *lang_note;
1826 const char *en_lang_note = N_("\n\nPlease write your report in English, if possible.");
1828 app = g_hash_table_find (apps, (GHRFunc)bugzilla_search_for_package, gopt_data.package);
1829 if (app == NULL) {
1830 /* Fallback to binary name */
1831 app = g_hash_table_lookup (apps, gopt_data.package);
1833 if (app == NULL) {
1834 buddy_error (NULL, _("Bug Buddy doesn't know how to send a suggestion for the application %s.\n"),
1835 gopt_data.package);
1836 return 0;
1839 g_object_set_data (G_OBJECT (ui), "app", app);
1841 if (app->icon) {
1842 GdkPixbuf *pixbuf;
1844 pixbuf = load_icon (app->icon);
1845 if (pixbuf) {
1846 gtk_image_set_from_pixbuf (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")),
1847 pixbuf);
1848 g_object_unref (pixbuf);
1851 fill_gnome_info (app, gnome_version, ui);
1852 fill_custom_info (app, ui);
1854 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1855 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1856 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1858 lang_note = gettext (en_lang_note);
1859 msg = g_strconcat (_("Thank you for helping us improving our software.\n"
1860 "Please fill your suggestions/error information for %s application.\n\n"
1861 "A valid email address is required. This will allow developers to "
1862 "contact you for more information if necessary."),
1863 strcmp (lang_note, en_lang_note)? lang_note: NULL,
1864 NULL);
1865 s = g_markup_printf_escaped (msg, app->name);
1866 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s);
1867 g_free (s);
1868 g_free (msg);
1870 s = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>",
1871 _("Suggestion / Error description:"));
1872 gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "main-label")), s);
1873 g_free (s);
1875 show_pending_checkbox_if_pending (ui);
1876 g_signal_connect (gtk_builder_get_object (ui, "send-button"), "clicked",
1877 G_CALLBACK (on_send_clicked), ui);
1879 email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"));
1880 g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui);
1882 default_email = get_default_user_email ();
1884 if (default_email != NULL) {
1885 gtk_entry_set_text (GTK_ENTRY (email_entry), default_email);
1886 g_free (default_email);
1888 g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_REQUEST));
1891 if (gopt_data.include_file != NULL) {
1892 fill_include_file (gopt_data.include_file, ui);
1896 g_signal_connect (gtk_builder_get_object (ui, "close-button"), "clicked",
1897 G_CALLBACK (close_callback), ui);
1899 gtk_main ();
1901 g_object_unref (program);
1903 return 0;