Fixed tpo in string. Fixes bug #611630
[bug-buddy.git] / src / bug-buddy.c
blob3df7d606c8dbc0c2c31053c45cc7643a616c512d
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 <gtk/gtk.h>
46 #include <gdk-pixbuf/gdk-pixbuf.h>
47 #include <gdk/gdkx.h>
48 #include <gdk/gdk.h>
49 #include <gdk/gdkkeysyms.h>
50 #include <bonobo-activation/bonobo-activation.h>
52 #include <libxml/tree.h>
53 #include <libxml/parser.h>
55 #include <gconf/gconf-client.h>
57 #include <libsoup/soup.h>
59 #define USE_PROXY_KEY "/system/http_proxy/use_http_proxy"
60 #define PROXY_HOST_KEY "/system/http_proxy/host"
61 #define PROXY_PORT_KEY "/system/http_proxy/port"
62 #define USE_PROXY_AUTH "/system/http_proxy/use_authentication"
63 #define PROXY_USER "/system/http_proxy/authentication_user"
64 #define PROXY_PASSWORD "/system/http_proxy/authentication_password"
65 #define ACCESSIBILITY_KEY "/desktop/gnome/interface/accessibility"
66 #define GTK_THEME_KEY "/desktop/gnome/interface/gtk_theme"
67 #define ICON_THEME_KEY "/desktop/gnome/interface/icon_theme"
68 #define GTK_MODULES_KEY "/apps/gnome_settings_daemon/gtk-modules"
69 #define DESKTOP_IS_HOME_DIR "/apps/nautilus/preferences/desktop_is_home_dir"
70 #define MIN_REPORT_DETAILS_CHARS 10
72 static GOptionData gopt_data;
73 static int bug_count = 0;
74 static GHashTable *apps = NULL;
76 static gboolean
77 show_version_cb (const char *option_name,
78 const char *value,
79 gpointer data,
80 GError **error)
82 g_print ("%s %s\n", _("GNOME Bug Buddy"), VERSION);
84 exit (EXIT_SUCCESS);
86 return FALSE;
89 static const GOptionEntry options[] = {
90 { "package", '\0', 0, G_OPTION_ARG_STRING, &gopt_data.package, N_("Package containing the program"), N_("PACKAGE") },
91 { "appname", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.app_file, N_("File name of crashed program"), N_("FILE") },
92 { "pid", '\0', 0, G_OPTION_ARG_INT, &gopt_data.pid, N_("PID of crashed program"), N_("PID") },
93 { "include", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.include_file, N_("Text file to include in the report"), N_("FILE") },
94 { "unlink-tempfile", '\0', 0, G_OPTION_ARG_NONE, &gopt_data.own_file, N_("Delete the included file after reporting"),NULL},
95 { "version", '\0', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, show_version_cb, NULL, NULL },
96 { NULL }
99 static void fill_stderr_info (GtkBuilder *ui);
100 static void fill_custom_info (BugzillaApplication *app, GtkBuilder *ui);
101 static void close_callback (GtkWidget *widget, gpointer user_data);
102 static void bug_buddy_quit (GtkBuilder *ui);
103 static void fill_include_file (char *filename, gboolean own_file, GtkBuilder *ui);
106 static void
107 buddy_error (GtkWidget *parent, const char *msg, ...)
109 GtkWidget *w;
110 GtkDialog *d;
111 gchar *s;
112 va_list args;
114 /* No va_list version of dialog_new, construct the string ourselves. */
115 va_start (args, msg);
116 s = g_strdup_vprintf (msg, args);
117 va_end (args);
119 w = gtk_message_dialog_new (GTK_WINDOW (parent),
121 GTK_MESSAGE_ERROR,
122 GTK_BUTTONS_OK,
123 "%s",
125 d = GTK_DIALOG (w);
126 gtk_dialog_set_default_response (d, GTK_RESPONSE_OK);
127 gtk_dialog_run (d);
128 gtk_widget_destroy (w);
129 g_free (s);
132 static void
133 lock_text (GtkBuilder *ui)
135 GtkTextView *text_view;
136 GtkTextBuffer *buffer;
137 GtkTextIter start;
138 GtkTextIter end;
139 static GtkTextTag *tag = NULL;
140 char *text;
142 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
143 buffer = gtk_text_view_get_buffer (text_view);
144 gtk_text_buffer_get_start_iter (buffer, &start);
145 gtk_text_buffer_get_end_iter (buffer, &end);
146 text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
148 if (!tag) {
149 GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (text_view));
150 tag = gtk_text_buffer_create_tag (buffer, "lock_tag",
151 "editable", FALSE,
152 /* I don't like how it looks like dimming also fg
153 "foreground-gdk", &style->fg[GTK_STATE_INSENSITIVE], */
154 "background-gdk", &style->bg[GTK_STATE_INSENSITIVE],
155 NULL);
158 if (gtk_text_iter_forward_search (&start, "Backtrace was generated from",
159 GTK_TEXT_SEARCH_TEXT_ONLY,
160 NULL, &end, NULL)) {
161 gtk_text_iter_forward_line (&end);
162 gtk_text_buffer_apply_tag_by_name (buffer, "lock_tag", &start, &end);
167 static gboolean
168 search_forbidden_words (GtkBuilder *ui)
170 GtkTextView *text_view;
171 GtkTextBuffer *buffer;
172 int i;
173 gboolean found = FALSE;
174 static GtkTextTag *tag = NULL;
176 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
177 buffer = gtk_text_view_get_buffer (text_view);
178 if (!tag) {
179 tag = gtk_text_buffer_create_tag (buffer, "forbbiden_tag",
180 "foreground", "white",
181 "background", "blue",
182 NULL);
185 for (i = 0; forbidden_words[i]; i++) {
186 GtkTextIter start;
187 GtkTextIter end;
189 gtk_text_buffer_get_start_iter (buffer, &start);
190 while (gtk_text_iter_forward_search (&start, forbidden_words[i],
191 GTK_TEXT_SEARCH_TEXT_ONLY,
192 &start, &end, NULL)) {
193 gtk_text_buffer_apply_tag_by_name (buffer, "forbbiden_tag", &start, &end);
194 start = end;
195 found = TRUE;
199 return found;
204 static void
205 copy_review (GtkWidget *button, gpointer data)
207 GtkTextView *text_view;
208 GtkTextBuffer *buffer;
209 GtkTextIter start;
210 GtkTextIter end;
211 GtkBuilder *ui = (GtkBuilder*) data;
213 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
214 buffer = gtk_text_view_get_buffer (text_view);
215 gtk_text_buffer_get_start_iter (buffer, &start);
216 gtk_text_buffer_get_end_iter (buffer, &end);
217 gtk_text_buffer_select_range (buffer, &start, &end);
218 gtk_text_buffer_copy_clipboard (buffer, gtk_clipboard_get (GDK_NONE));
222 static void
223 edit_review (GtkWidget *button, gpointer data)
225 GtkTextView *text_view;
226 GtkBuilder *ui = (GtkBuilder*) data;
227 gboolean editable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button));
229 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
230 gtk_text_view_set_editable (text_view, editable);
231 if (editable) {
232 lock_text (ui);
237 static void
238 close_review (GtkWidget *button, gpointer data)
240 GtkWidget *review_dialog = GTK_WIDGET (data);
242 gtk_widget_hide (review_dialog);
245 static gboolean
246 delete_review (GtkWidget *widget, GdkEvent *event, gpointer user_data)
248 gtk_widget_hide (widget);
250 return TRUE; /* don't destroy */
253 static void
254 show_review (GtkWidget *button, gpointer data)
256 GtkWidget *review_dialog, *main_window;
257 GtkWidget *edit, *copy, *close;
258 GtkBuilder *ui = (GtkBuilder*) data;
259 static gboolean initialized = FALSE;
261 if (!initialized) {
262 review_dialog = GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog"));
263 main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window"));
264 copy = GTK_WIDGET (gtk_builder_get_object (ui, "copy-review-button"));
265 edit = GTK_WIDGET (gtk_builder_get_object (ui, "edit-review-button"));
266 close = GTK_WIDGET (gtk_builder_get_object (ui, "close-review-button"));
268 gtk_window_set_transient_for (GTK_WINDOW (review_dialog), GTK_WINDOW (main_window));
270 g_signal_connect (G_OBJECT (copy), "clicked", G_CALLBACK (copy_review), ui);
271 g_signal_connect (G_OBJECT (edit), "toggled", G_CALLBACK (edit_review), ui);
272 g_signal_connect (G_OBJECT (close), "clicked", G_CALLBACK (close_review), review_dialog);
273 g_signal_connect (G_OBJECT (review_dialog), "delete-event", G_CALLBACK (delete_review), NULL);
275 initialized = TRUE;
278 lock_text (ui);
280 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog")));
284 static GnomeVersionInfo*
285 get_gnome_version_info (void)
287 GnomeVersionInfo *version;
288 xmlDoc *doc;
289 char *xml_file;
290 xmlNode *node;
291 guchar *platform, *minor, *micro, *distributor, *date;
293 version = g_new0 (GnomeVersionInfo, 1);
295 xml_file = g_build_filename (DATADIR, "gnome-about/gnome-version.xml", NULL);
297 doc = xmlParseFile (xml_file);
298 g_free (xml_file);
300 if (!doc)
301 return NULL;
303 platform = minor = micro = distributor = date = NULL;
305 for (node = xmlDocGetRootElement (doc)->children; node; node = node->next) {
306 if (!strcmp ((char *)node->name, "platform"))
307 platform = xmlNodeGetContent (node);
308 else if (!strcmp ((char *)node->name, "minor"))
309 minor = xmlNodeGetContent (node);
310 else if (!strcmp ((char *)node->name, "micro"))
311 micro = xmlNodeGetContent (node);
312 else if (!strcmp ((char *)node->name, "distributor"))
313 distributor = xmlNodeGetContent (node);
314 else if (!strcmp ((char *)node->name, "date"))
315 date = xmlNodeGetContent (node);
318 if (platform && minor && micro)
319 version->gnome_platform = g_strdup_printf ("%s.%s.%s", platform, minor, micro);
321 if (distributor && *distributor)
322 version->gnome_distributor = g_strdup ((char *)distributor);
324 if (date && *date)
325 version->gnome_date = g_strdup ((char *)date);
327 xmlFree (platform);
328 xmlFree (minor);
329 xmlFree (micro);
330 xmlFree (distributor);
331 xmlFree (date);
333 xmlFreeDoc (doc);
335 return version;
338 static gboolean
339 update_progress_bar (gpointer data)
341 GtkProgressBar *pbar = GTK_PROGRESS_BAR (data);
343 gtk_progress_bar_pulse (pbar);
345 return TRUE;
348 static void
349 save_email (const char *email)
351 GConfClient *conf_client;
353 conf_client = gconf_client_get_default ();
354 gconf_client_set_string (conf_client, "/apps/bug-buddy/email_address", email, NULL);
355 g_object_unref (conf_client);
358 static void
359 copy_link_item_activated_cb (GtkMenuItem *menu_item,
360 const gchar *link)
362 GtkClipboard *clipboard;
364 clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
365 gtk_clipboard_set_text (clipboard, link, -1);
368 static GtkWidget *
369 build_link_menu (const gchar *link)
371 GtkWidget *menu;
372 GtkWidget *item;
373 GtkWidget *image;
375 menu = gtk_menu_new ();
376 item = gtk_image_menu_item_new_with_mnemonic (_("Copy _Link Address"));
377 image = gtk_image_new_from_stock (GTK_STOCK_COPY, GTK_ICON_SIZE_MENU);
378 gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
379 gtk_widget_show_all (item);
380 g_signal_connect (item, "activate",
381 G_CALLBACK (copy_link_item_activated_cb), (char *) link);
382 gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
384 return menu;
387 static gboolean
388 link_button_press_event_cb (GtkWidget *widget,
389 GdkEventButton *event,
390 gpointer data)
392 const gchar *link = gtk_link_button_get_uri (GTK_LINK_BUTTON (widget));
394 if (event->type == GDK_BUTTON_PRESS && event->button == 3) {
395 GtkWidget *popup;
397 popup = build_link_menu (link);
398 gtk_menu_popup (GTK_MENU (popup), NULL, NULL, NULL, NULL,
399 event->button, event->time);
401 return TRUE;
404 return FALSE;
407 static void
408 link_callback (GtkLinkButton *button, gpointer user_data)
410 const gchar *link = gtk_link_button_get_uri (button);
411 GdkAppLaunchContext *context;
413 context = gdk_app_launch_context_new ();
414 gdk_app_launch_context_set_screen (context,
415 gtk_widget_get_screen (GTK_WIDGET (button)));
416 gdk_app_launch_context_set_timestamp (context,
417 gtk_get_current_event_time ());
419 if (!g_app_info_launch_default_for_uri (link,
420 G_APP_LAUNCH_CONTEXT (context),
421 NULL))
423 char *text;
425 text = g_markup_printf_escaped (_("Bug Buddy was unable to display the link \"%s\"\n"), link);
426 buddy_error (NULL, text);
427 g_free (text);
430 g_object_unref (context);
432 return;
435 static void
436 save_to_file (const gchar *filename, const gchar *text)
438 GError *error = NULL;
440 if (!g_file_set_contents (filename, text, -1, &error)) {
441 g_warning ("Unable to save document %s: %s\n", filename, error->message);
442 g_error_free (error);
447 static void
448 network_error (SoupMessage *msg, GtkBuilder *ui)
451 GtkWidget *dialog;
452 int res;
454 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
456 dialog = gtk_message_dialog_new_with_markup (NULL,
457 GTK_DIALOG_MODAL,
458 GTK_MESSAGE_WARNING,
459 GTK_BUTTONS_YES_NO,
460 _("There was a network error while sending the report. "
461 "Do you want to save this report and send it later?"));
462 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
463 "%s", _("Please ensure that your Internet connection is active "
464 "and working correctly."));
465 res = gtk_dialog_run (GTK_DIALOG (dialog));
466 gtk_widget_destroy (dialog);
468 if (res == GTK_RESPONSE_YES) {
469 gchar *dirname;
470 gchar *filename;
472 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
473 if (!g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
474 g_mkdir_with_parents (dirname, 0755);
477 filename = g_strdup_printf ("%s/%ld", dirname, (long)time (NULL));
479 save_to_file (filename, msg->request_body->data);
481 g_free (dirname);
482 g_free (filename);
485 bug_buddy_quit (ui);
486 return;
489 static void
490 remove_pending_reports (void)
492 GDir *dir;
493 char *dirname;
494 GError *error = NULL;
496 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
497 dir = g_dir_open (dirname, 0, &error);
498 if (dir) {
499 const char *name = g_dir_read_name (dir);
500 while (name) {
501 char *path = g_strdup_printf ("%s/%s", dirname, name);
502 g_remove (path);
503 g_free (path);
504 name = g_dir_read_name (dir);
506 g_dir_close (dir);
509 g_remove (dirname);
510 g_free (dirname);
513 static void
514 all_sent (GtkBuilder *ui)
516 GtkWidget *close_button;
518 /* hide the progressbar */
519 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
521 close_button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
522 gtk_widget_show (close_button);
525 static void
526 previous_sent (SoupSession *session, SoupMessage *msg, GtkBuilder *ui)
528 if (--bug_count == 0) {
529 all_sent (ui);
534 static void
535 bug_sent (SoupSession *session, SoupMessage *msg, GtkBuilder *ui)
537 GtkWidget *button;
538 GtkWidget *image;
539 char *text = NULL;
540 char *errmsg = NULL;
541 char *str = NULL;
542 long bugid;
543 char *response;
544 char *tmp;
545 GtkWidget *urlbutton;
546 GtkRequisition requisition;
547 GError *err = NULL;
549 button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
550 gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
551 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
553 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON),
554 gtk_button_set_image (GTK_BUTTON (button), image);
556 if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
557 network_error (msg, ui);
558 } else {
559 remove_pending_reports ();
562 /* parse the XML-RPC response */
563 response = bugzilla_parse_response (msg, &err);
564 if (response != NULL) {
565 bugid = strtol (response, &tmp, 10);
566 GtkWidget *main_vbox;
568 /* we need a reference to the vbox containing the text so that we
569 * can add a GtkLinkButton to the bug report */
570 main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox"));
572 if (response == tmp) {
573 char *url;
574 url = strstr (response, "ViewURL=");
575 if (url)
576 url += strlen ("ViewURL=");
577 else
578 url = "";
580 text = g_strdup (url);
581 } else
582 text = g_strdup_printf ("http://bugzilla.gnome.org/show_bug.cgi?id=%ld", bugid);
584 /* create a clickable link to the bug report */
585 urlbutton = gtk_link_button_new (text);
586 g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL);
587 g_signal_connect (G_OBJECT (urlbutton), "button-press-event",
588 G_CALLBACK (link_button_press_event_cb), NULL);
589 gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0);
591 gtk_widget_show (urlbutton);
592 g_free (text);
594 text = g_markup_printf_escaped (_("A bug report detailing your software crash has been sent to GNOME. "
595 "This information will allow the developers to understand the cause "
596 "of the crash and prepare a solution for it.\n\n"
597 "You may be contacted by a GNOME developer if more details are "
598 "required about the crash.\n\n"
599 "You can view your bug report and follow its progress with this URL:\n")) ;
601 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), text);
602 g_free (text);
603 save_email (gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry"))));
604 } else {
605 errmsg = _("Bug Buddy encountered an error while submitting your report "
606 "to the Bugzilla server. Details of the error are included below.\n\n");
608 if (err && err->domain == SOUP_XMLRPC_FAULT) {
609 /* see http://cvs.gnome.org/viewcvs/bugzilla-newer/Bugzilla/RPC.pm?view=markup */
610 if (err->message == NULL) {
611 text = g_strdup_printf (_("Bugzilla reported an error when trying to process your "
612 "request, but was unable to parse the response."));
613 } else if (g_str_equal (err->message, "invalid_username")) {
614 text = g_strdup_printf (_("The email address you provided is not valid."));
615 } else if (g_str_equal (err->message, "account_disabled")) {
616 text = g_strdup_printf (_("The account associated with the email address "
617 "provided has been disabled."));
618 } else if (g_str_equal (err->message, "product_doesnt_exist")) {
619 text = g_strdup_printf (_("The product specified doesn't exist or has been "
620 "renamed. Please upgrade to the latest version."));
621 } else if (g_str_equal (err->message, "component_not_valid")) {
622 text = g_strdup_printf (_("The component specified doesn't exist or has been "
623 "renamed. Please upgrade to the latest version."));
624 } else if (g_str_equal (err->message, "require_summary")) {
625 text = g_strdup_printf (_("The summary is required in your bug report. "
626 "This should not happen with the latest Bug Buddy."));
627 } else if (g_str_equal (err->message, "description_required")) {
628 text = g_strdup_printf (_("The description is required in your bug report. "
629 "This should not happen with the latest Bug Buddy."));
630 } else {
631 text = g_strdup_printf (_("The fault code returned by Bugzilla is not recognized. "
632 "Please report the following information to "
633 "bugzilla.gnome.org manually:\n\n%s"), err->message);
635 } else if (err) {
636 switch (err->code) {
637 case BUGZILLA_ERROR_RECV_BAD_STATUS:
638 text = g_strdup_printf (_("Server returned bad state. This is most likely a server "
639 "issue and should be reported to bugmaster@gnome.org\n\n%s"),
640 err->message);
641 break;
642 case BUGZILLA_ERROR_RECV_PARSE_FAILED:
643 text = g_strdup_printf (_("Failed to parse the XML-RPC response. Response follows:\n\n%s"),
644 err->message);
645 break;
646 default:
647 text = g_strdup_printf (_("An unknown error occurred. This is most likely a problem with "
648 "Bug Buddy. Please report this problem manually at bugzilla."
649 "gnome.org\n\n"));
650 break;
653 str = g_strconcat (errmsg, text, NULL);
654 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), str);
656 g_free (str);
657 g_free (text);
658 g_error_free (err);
662 if (--bug_count == 0) {
663 all_sent (ui);
666 gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui, "main-window")), &requisition);
667 gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
668 requisition.width, requisition.height);
670 g_free (response);
675 static void
676 set_proxy (SoupSession *session)
678 GConfClient *gconf_client;
679 char *host;
680 int port;
681 char *proxy_uri;
682 SoupURI *uri;
683 char *username = NULL;
684 char *password = NULL;
687 gconf_client = gconf_client_get_default ();
689 if (gconf_client_get_bool (gconf_client, USE_PROXY_KEY, NULL) == FALSE) {
690 g_object_unref (gconf_client);
691 return;
694 host = gconf_client_get_string (gconf_client, PROXY_HOST_KEY, NULL);
695 if (host == NULL) {
696 g_object_unref (gconf_client);
697 return;
699 port = gconf_client_get_int (gconf_client, PROXY_PORT_KEY, NULL);
700 if (port == 0)
701 port = 80;
703 if (gconf_client_get_bool (gconf_client, USE_PROXY_AUTH, NULL)) {
704 username = gconf_client_get_string (gconf_client, PROXY_USER, NULL);
705 password = gconf_client_get_string (gconf_client, PROXY_PASSWORD, NULL);
708 if (username && password)
709 proxy_uri = g_strdup_printf ("http://%s:%s@%s:%d", username, password, host, port);
710 else
711 proxy_uri = g_strdup_printf ("http://%s:%d", host, port);
713 uri = soup_uri_new (proxy_uri);
714 g_object_set (G_OBJECT (session), "proxy-uri", uri, NULL);
716 g_free (host);
717 g_free (username);
718 g_free (password);
719 g_free (proxy_uri);
720 soup_uri_free (uri);
721 g_object_unref (gconf_client);
726 static char*
727 create_report_title (BugzillaApplication *app, int type, const char *description)
729 char *title;
730 long size = 0;
731 char *tmp = NULL;
733 if (description) {
734 tmp = g_malloc0 (256); /* This should be safe enough for 24 UTF-8 chars.
735 * anyway, I miss a g_utf8_strndup :) */
736 size = g_utf8_strlen (description, -1);
737 if (size > 24) {
738 g_utf8_strncpy (tmp, description, 24);
739 } else {
740 g_utf8_strncpy (tmp, description, size);
744 if (type == BUG_TYPE_CRASH) {
745 title = g_strdup_printf ("crash in %s: %s%s", app->cname,
746 tmp ? tmp : "empty description",
747 (tmp && size > 24) ? "..." : "");
748 } else {
749 title = g_strdup_printf ("%s: %s%s", app->cname,
750 tmp ? tmp : "empty description",
751 (tmp && size > 24) ? "..." : "");
754 g_free (tmp);
756 return title;
758 static void
759 send_report (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui)
761 GtkTextView *text_view;
762 GtkTextBuffer *buffer;
763 GtkTextIter start;
764 GtkTextIter end;
765 int type;
766 char *gdb_text;
767 char *details_text;
768 char *title;
769 char *final_text;
770 const char *email;
771 SoupSession *session;
772 SoupMessage *message;
773 GError *err = NULL;
775 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check")));
777 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
778 buffer = gtk_text_view_get_buffer (text_view);
779 gtk_text_buffer_get_start_iter (buffer, &start);
780 gtk_text_buffer_get_end_iter (buffer, &end);
781 gdb_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
783 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "details-view"));
784 buffer = gtk_text_view_get_buffer (text_view);
785 gtk_text_buffer_get_start_iter (buffer, &start);
786 gtk_text_buffer_get_end_iter (buffer, &end);
787 details_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
789 type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (ui), "type"));
790 final_text = g_strdup_printf ("%s%s\n\n\n%s",
791 type == BUG_TYPE_CRASH ? "What were you doing when the application crashed?\n" : "",
792 details_text != NULL ? details_text : "",
793 gdb_text != NULL ? gdb_text : "<empty backtrace>");
795 email = gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry")));
796 title = create_report_title (app, type, details_text);
798 message = bugzilla_create_report (app, type, gnome_version, email, title, final_text, ui, &err);
799 if (message == NULL) {
800 char *text;
802 if (err != NULL) {
803 text = g_strdup_printf (_("Unable to create the bug report: %s\n"), err->message);
804 } else {
805 text = g_strdup_printf (_("There was an error creating the bug report\n"));
808 buddy_error (NULL, text);
809 g_free (text);
810 g_free (gdb_text);
811 g_free (details_text);
812 g_free (title);
813 g_free (final_text);
814 bug_buddy_quit (ui);
815 return;
818 session = soup_session_async_new ();
819 set_proxy (session);
822 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ui, "pending-reports-check")))) {
823 GDir *dir;
824 char *dirname;
825 GError *error = NULL;
827 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
828 dir = g_dir_open (dirname, 0, &error);
829 if (dir) {
830 const char *name = g_dir_read_name (dir);
831 while (name != NULL) {
832 char *path;
833 char *contents;
834 gsize length;
836 path = g_strdup_printf ("%s/%s", dirname, name);
837 if (g_file_get_contents (path, &contents, &length, NULL)) {
838 SoupMessage *msg;
839 msg = soup_message_new ("POST", "http://bugzilla.gnome.org/bugbuddy.cgi");
840 soup_message_set_request (msg, "text/xml",
841 SOUP_MEMORY_TAKE,
842 contents, length);
843 bug_count++;
844 soup_session_queue_message (session, SOUP_MESSAGE (msg),
845 (SoupSessionCallback)previous_sent, ui);
847 g_free (path);
848 name = g_dir_read_name (dir);
851 g_dir_close (dir);
855 bug_count++;
857 soup_session_queue_message (session, message,
858 (SoupSessionCallback)bug_sent, ui);
859 g_free (gdb_text);
860 g_free (details_text);
861 g_free (title);
862 g_free (final_text);
864 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
865 _("Sending…"));
869 /* A local part is valid if it is one or more valid characters. */
870 static gboolean
871 email_local_part_is_valid (const char *local_part)
873 const char *character;
875 if (!local_part[0])
876 return FALSE;
878 for (character = local_part; *character; character++) {
879 /* RFC 3696 says *any* printable ASCII character can
880 * appear in local-part, subject to quoting rules. */
881 if (g_ascii_isprint (*character))
882 continue;
884 /* Not valid character, not valid local part. */
885 return FALSE;
888 return TRUE;
891 /* A domain label is valid if it is one or more valid characters. */
892 static gboolean
893 email_domain_label_is_valid (const char *domain_label)
895 const char *character;
896 int i;
898 /* Validate each character, whilst measuring length, i. */
899 for (i = 0; *(character = domain_label + i); i++) {
901 /* If character is alphanumeric it is valid. */
902 if (g_ascii_isalnum (*character))
903 continue;
905 /* If it's a hyphen, it's also valid. */
906 if (*character == '-')
907 continue;
909 /* Anything else is invalid */
910 return FALSE;
913 /* Labels must be between 1 and 63 characters long */
914 if (i < 1 || i > 63) {
915 return FALSE;
918 return TRUE;
921 /* A domain is valid if it is one or more valid, dot-separated labels. */
922 static gboolean
923 email_domain_is_valid (const char *domain)
925 char **labels;
926 char **this_label;
927 gboolean retval = FALSE;
929 /* If there is no domain, there are no domain labels and the domain is
930 * not valid. */
931 if (!domain[0])
932 return FALSE;
934 /* Split the domain on the dot to validate labels. */
935 labels = g_strsplit (domain, ".", 0);
936 if (g_strv_length (labels) == 1) {
937 /* the domain doesn't contain any dot: it's not valid */
938 goto out;
941 for (this_label = labels; *this_label; this_label++) {
942 if (!email_domain_label_is_valid (*this_label)) {
943 goto out;
947 retval = TRUE;
949 out:
950 g_strfreev (labels);
951 return retval;
954 /* Check for *simple* email addresses of the form user@host, with checks
955 * in characters used, and sanity checks on the form of host.
957 /* FIXME: Should we provide a useful error message? */
958 static gboolean
959 email_is_valid (const char *address)
961 char *local_part;
962 char *domain;
963 char *at_sign;
964 gboolean is_valid;
966 /* Split on the *last* '@' character: */
967 at_sign = strrchr (address, '@');
969 if (at_sign == NULL)
970 return FALSE;
972 local_part = g_strndup (address, at_sign - address);
973 domain = g_strdup (at_sign + 1);
975 /* Check each part is valid */
976 is_valid = email_local_part_is_valid (local_part)
977 && email_domain_is_valid (domain);
979 g_free (local_part);
980 g_free (domain);
982 return is_valid;
985 static void
986 check_email (GtkEditable *editable, gpointer data)
988 const char *email;
989 GtkBuilder *ui = (GtkBuilder*) data;
991 email = gtk_entry_get_text (GTK_ENTRY (editable));
992 gtk_widget_set_sensitive (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")),
993 email_is_valid (email));
997 static void
998 on_send_clicked (GtkWidget *button, gpointer data)
1000 BugzillaApplication *app;
1001 GnomeVersionInfo *gnome_version;
1002 GtkRequisition requisition;
1003 GtkBuilder *ui = (GtkBuilder*) data;
1004 GtkWidget *details;
1005 int i;
1007 app = g_object_get_data (G_OBJECT (ui), "app");
1008 gnome_version = g_object_get_data (G_OBJECT (ui), "gnome-version");
1010 details = GTK_WIDGET (gtk_builder_get_object (ui, "details-view"));
1011 i = gtk_text_buffer_get_char_count (
1012 gtk_text_view_get_buffer (GTK_TEXT_VIEW (details)));
1014 if (i < MIN_REPORT_DETAILS_CHARS) {
1015 GtkWidget *dialog;
1016 GtkWidget *button;
1017 GtkWidget *icon;
1019 int i;
1021 dialog = gtk_message_dialog_new (NULL,
1022 GTK_DIALOG_MODAL,
1023 GTK_MESSAGE_INFO,
1024 GTK_BUTTONS_NONE,
1025 _("The description you provided for the "
1026 "crash is very short. Are you sure you want "
1027 "to send it?"));
1029 /* Secondary text */
1030 gtk_message_dialog_format_secondary_text
1031 (GTK_MESSAGE_DIALOG (dialog),
1032 _("A short description is probably not of much help "
1033 "to the developers investigating your report. "
1034 "If you provide a better one, for instance "
1035 "specifying a way to reproduce the crash, the "
1036 "issue can be more easily resolved."));
1038 /* Review button */
1039 button = gtk_dialog_add_button (GTK_DIALOG (dialog),
1040 _("_Review description"),
1041 GTK_RESPONSE_CANCEL);
1042 icon = gtk_image_new_from_stock
1043 (GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON);
1044 gtk_button_set_image (GTK_BUTTON (button), icon);
1045 gtk_widget_show (button);
1047 /* Send anyway button */
1048 button = gtk_dialog_add_button (GTK_DIALOG (dialog),
1049 _("_Send anyway"),
1050 GTK_RESPONSE_OK);
1051 icon = gtk_image_new_from_stock
1052 (GTK_STOCK_OK, GTK_ICON_SIZE_BUTTON);
1053 gtk_button_set_image (GTK_BUTTON (button), icon);
1054 gtk_widget_show (button);
1056 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
1057 GTK_RESPONSE_OK);
1059 i = gtk_dialog_run (GTK_DIALOG (dialog));
1061 gtk_widget_destroy (dialog);
1063 if (i != GTK_RESPONSE_OK)
1064 return;
1067 /* hide the send button immediately so that the user can't click
1068 * it more than once (this will create multiple bugs).
1070 gtk_widget_hide (GTK_WIDGET (
1071 gtk_builder_get_object (ui, "send-button")));
1073 gtk_widget_show (GTK_WIDGET (
1074 gtk_builder_get_object (ui, "progressbar")));
1075 gtk_widget_hide (GTK_WIDGET (
1076 gtk_builder_get_object (ui, "final-box")));
1077 gtk_widget_hide (GTK_WIDGET (
1078 gtk_builder_get_object (ui, "review-box")));
1080 gtk_widget_size_request (GTK_WIDGET (
1081 gtk_builder_get_object (ui, "main-window")),
1082 &requisition);
1084 gtk_window_resize (GTK_WINDOW (
1085 gtk_builder_get_object (ui, "main-window")),
1086 requisition.width, requisition.height);
1088 send_report (app, gnome_version, ui);
1092 static gboolean
1093 gdb_insert_text (const gchar *stacktrace, GtkBuilder *ui)
1095 GtkTextView *text_view;
1096 GtkTextIter end;
1097 GtkTextBuffer *buffer;
1099 /* FIXME: These strings are gdb specific, we should add here also dbx */
1100 const char *bt_step1 = "#1";
1101 const char *bt_step2 = "#2";
1102 const char *bt_step3 = "#3";
1104 if (!g_strrstr (stacktrace, bt_step1) &&
1105 !g_strrstr (stacktrace, bt_step2) &&
1106 !g_strrstr (stacktrace, bt_step3)) {
1107 return FALSE;
1112 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1113 buffer = gtk_text_view_get_buffer (text_view);
1114 gtk_text_buffer_get_end_iter (buffer, &end);
1116 /* add the stacktrace to the GtkTextView */
1117 gtk_text_buffer_insert (buffer, &end, stacktrace, strlen (stacktrace));
1119 return TRUE;
1122 static void
1123 show_pending_checkbox_if_pending (GtkBuilder *ui)
1125 char *dirname;
1126 GtkWidget *check;
1128 dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ());
1129 if (g_file_test (dirname, G_FILE_TEST_IS_DIR)) {
1130 check = GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check"));
1131 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE);
1132 gtk_widget_show (check);
1134 g_free (dirname);
1137 static GtkWidget*
1138 create_debuginfo_link (void)
1140 GtkWidget *urlbutton;
1142 /* create a clickable link to the bug report */
1143 urlbutton = gtk_link_button_new_with_label ("http://live.gnome.org/GettingTraces/DistroSpecificInstructions",
1144 /* Translators: This is the hyperlink which takes to http://live.gnome.org/GettingTraces/DistroSpecificInstructions
1145 * page. Please also mention that the page is in English */
1146 _("Getting useful crash reports"));
1147 g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL);
1149 return urlbutton;
1152 static void
1153 useless_finished (GtkBuilder *ui)
1156 GtkWidget *button, *image, *main_vbox, *urlbutton;
1157 BugzillaApplication *app;
1158 char *label_text;
1160 app = g_object_get_data (G_OBJECT (ui), "app");
1162 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1164 label_text = g_markup_printf_escaped (_("The application %s crashed. The bug reporting tool was "
1165 "unable to collect enough information about the crash to be "
1166 "useful to the developers.\n\n"
1167 "In order to submit useful reports, please consider installing "
1168 "debug packages for your distribution.\n"
1169 "Click the link below to get information about how to install "
1170 "these packages:\n"),
1171 app->name);
1172 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
1173 label_text);
1175 main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox"));
1176 urlbutton = create_debuginfo_link ();
1177 gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0);
1179 gtk_widget_show (urlbutton);
1181 g_free (label_text);
1183 button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button"));
1184 gtk_button_set_label (GTK_BUTTON (button), _("_Close"));
1185 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
1187 image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON),
1188 gtk_button_set_image (GTK_BUTTON (button), image);
1190 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "email-entry")));
1194 static void
1195 known_app_finished (GtkBuilder *ui)
1197 BugzillaApplication *app;
1198 GtkWidget *email_entry;
1199 GtkWidget *button;
1200 char *default_email;
1201 char *lang_note, *label_text, *s;
1202 const char *en_lang_note = N_("\n\nPlease write your report in English, if possible.");
1204 app = g_object_get_data (G_OBJECT (ui), "app");
1206 fill_custom_info (app, ui);
1207 fill_stderr_info (ui);
1209 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1210 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1211 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1213 lang_note = gettext (en_lang_note);
1215 label_text = g_strconcat (_("Information about the %s application crash has been successfully collected. "
1216 "Please provide some more details about what you were doing when "
1217 "the application crashed.\n\n"
1219 "A valid email address is required. This will allow the developers to "
1220 "contact you for more information if necessary."),
1221 strcmp (lang_note, en_lang_note) ? lang_note : NULL,
1222 NULL);
1224 s = g_markup_printf_escaped (label_text, app->name);
1226 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")),
1229 g_free (s);
1230 g_free (label_text);
1232 show_pending_checkbox_if_pending (ui);
1234 button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button"));
1235 g_signal_connect (button, "clicked",
1236 G_CALLBACK (on_send_clicked), ui);
1238 email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"));
1239 g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui);
1241 default_email = get_default_user_email ();
1243 if (default_email != NULL) {
1244 gtk_entry_set_text (GTK_ENTRY (email_entry), default_email);
1245 g_free (default_email);
1246 } else {
1247 gtk_widget_set_sensitive (button, FALSE);
1250 if (search_forbidden_words (ui)) {
1251 char *review_text = g_markup_printf_escaped ("<small><i><span weight=\"bold\">%s</span> %s</i></small>",
1252 _("WARNING:"),
1253 _("Some sensitive data is likely present in the crash details. "
1254 "Please review and edit the information if you are concerned "
1255 "about transmitting passwords or other sensitive data."));
1256 gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "review-label")), review_text);
1258 g_signal_connect (gtk_builder_get_object (ui, "review-button"), "clicked", G_CALLBACK (show_review), ui);
1259 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-box")));
1261 gtk_widget_grab_focus (GTK_WIDGET (gtk_builder_get_object (ui, "details-view")));
1264 static void
1265 gdb_finished (const gchar *stacktrace, gpointer data)
1267 GtkBuilder *ui = (GtkBuilder*) data;
1269 if (gdb_insert_text (stacktrace, ui)) {
1270 known_app_finished (ui);
1271 } else {
1272 useless_finished (ui);
1278 static void
1279 on_save_clicked (GtkWidget *button, gpointer user_data)
1281 GtkBuilder *ui = (GtkBuilder *)user_data;
1282 GtkWidget *dialog;
1283 const char *desktop;
1284 char *filename;
1285 gboolean desktop_is_home_dir, saved;
1286 GConfClient *gconf_client;
1288 saved = FALSE;
1290 dialog = gtk_file_chooser_dialog_new (_("Save File"),
1291 GTK_WINDOW (gtk_builder_get_object (ui, "main-window")),
1292 GTK_FILE_CHOOSER_ACTION_SAVE,
1293 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1294 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1295 NULL);
1297 gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE);
1299 gconf_client = gconf_client_get_default ();
1300 desktop_is_home_dir = gconf_client_get_bool (gconf_client, DESKTOP_IS_HOME_DIR, NULL);
1301 g_object_unref (gconf_client);
1303 if (desktop_is_home_dir)
1304 desktop = g_get_home_dir();
1305 else
1306 desktop = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
1308 gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), desktop);
1310 filename = g_strconcat (gopt_data.app_file, _("-bugreport.txt"), NULL);
1311 gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename);
1312 g_free (filename);
1314 if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) {
1315 char *filename;
1316 GtkTextView *text_view;
1317 GtkTextBuffer *buffer;
1318 GtkTextIter start;
1319 GtkTextIter end;
1320 gchar *text;
1322 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1323 buffer = gtk_text_view_get_buffer (text_view);
1324 gtk_text_buffer_get_start_iter (buffer, &start);
1325 gtk_text_buffer_get_end_iter (buffer, &end);
1326 text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
1328 filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
1329 save_to_file (filename, text);
1330 g_free (filename);
1331 g_free (text);
1332 saved = TRUE;
1335 gtk_widget_destroy (dialog);
1336 if (saved) {
1337 bug_buddy_quit (ui);
1341 static void
1342 focus_details (GtkWidget *widget, gpointer data)
1344 gtk_widget_grab_focus (widget);
1349 static void
1350 unknown_app_finished (GtkBuilder *ui)
1352 GtkWidget *button;
1353 char *label_text;
1355 /* add the include file information now that gdb has run */
1356 if (gopt_data.include_file != NULL) {
1357 fill_include_file (gopt_data.include_file, gopt_data.own_file, ui);
1360 fill_stderr_info (ui);
1362 /* don't need user input, so hide these widgets */
1363 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1364 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
1366 /* make the send button into a save button :-) */
1367 button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button"));
1368 gtk_button_set_label (GTK_BUTTON (button), _("_Save Bug Report"));
1369 gtk_button_set_use_underline (GTK_BUTTON (button), TRUE);
1370 g_signal_connect (GTK_BUTTON (button), "clicked", G_CALLBACK (on_save_clicked), ui);
1371 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
1373 label_text = g_markup_printf_escaped (_("The application %s has crashed.\n"
1374 "Information about the crash has been successfully collected.\n\n"
1375 "This application is not known to Bug Buddy, therefore the "
1376 "bug report cannot be sent to the GNOME Bugzilla. Please save the "
1377 "bug to a text file and report it to the appropriate bug tracker "
1378 "for this application."), gopt_data.app_file);
1379 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), label_text);
1381 /* FIXME: If we just grab the focus here to the GtkTextView it will crash on the blink_cb because
1382 * the window is nop mapped! Is this a gtk+ bug? are we doing something wrong?
1383 * Let's do a funny Workaround: */
1384 g_signal_connect_after (gtk_builder_get_object (ui, "details-view"), "realize", G_CALLBACK (focus_details), NULL);
1385 gtk_widget_realize (GTK_WIDGET (gtk_builder_get_object (ui, "details-view")));
1391 static void
1392 gdb_finished_unknown_app (const gchar *stacktrace, gpointer data)
1394 GtkBuilder *ui = (GtkBuilder*) data;
1396 gdb_insert_text (stacktrace, ui);
1397 unknown_app_finished (ui);
1400 static void
1401 bug_buddy_quit (GtkBuilder *ui)
1403 gpointer data;
1405 g_return_if_fail (ui != NULL);
1407 data = g_object_get_data (G_OBJECT (ui), "sourceid");
1409 if (data != NULL) {
1410 guint source_id = GPOINTER_TO_UINT (data);
1412 /* removes the context from the main loop and kills any remaining
1413 * gdb process */
1414 if (source_id > 0) {
1415 g_source_remove (source_id);
1416 g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (0));
1420 g_hash_table_destroy (apps);
1422 g_object_unref (ui);
1424 gtk_main_quit ();
1427 static gboolean
1428 keypress_callback (GtkWidget *widget, GdkEventKey *event, gpointer data)
1430 if (event->keyval == GDK_Escape) {
1431 close_callback (NULL, data);
1432 return TRUE;
1434 /* let others handle the event */
1435 return FALSE;
1438 static void
1439 close_callback (GtkWidget *widget, gpointer user_data)
1441 GtkBuilder *ui = (GtkBuilder *)user_data;
1443 bug_buddy_quit (ui);
1446 static void
1447 help_callback (GtkWidget *widget, gpointer user_data)
1449 GError *error = NULL;
1450 gboolean res;
1452 res = gtk_show_uri (gtk_widget_get_screen (widget),
1453 "ghelp:user-guide#feedback-bugs",
1454 gtk_get_current_event_time (), &error);
1456 if (!res) {
1457 GtkWidget *error_dialog =
1458 gtk_message_dialog_new (NULL,
1459 GTK_DIALOG_MODAL,
1460 GTK_MESSAGE_ERROR,
1461 GTK_BUTTONS_CLOSE,
1462 _("There was an error displaying help: %s"),
1463 error->message);
1465 g_signal_connect (G_OBJECT (error_dialog), "response",
1466 G_CALLBACK (gtk_widget_destroy), NULL);
1468 gtk_window_set_resizable (GTK_WINDOW (error_dialog), FALSE);
1470 gtk_widget_show (error_dialog);
1471 g_error_free (error);
1472 error = NULL;
1476 static gboolean
1477 delete_callback (GtkWidget *widget, GdkEvent *event, gpointer data)
1479 close_callback (NULL, data);
1480 return TRUE;
1483 static void
1484 fill_gnome_info (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui)
1486 char *version_info;
1487 char *distro;
1488 GtkTextView *text_view;
1489 GtkTextIter end;
1490 GtkTextBuffer *buffer;
1492 g_return_if_fail (app != NULL);
1493 g_return_if_fail (gnome_version != NULL);
1494 g_return_if_fail (ui != NULL);
1496 distro = get_distro_name ();
1497 version_info = g_strdup_printf ("Distribution: %s\n"
1498 "Gnome Release: %s %s (%s)\n"
1499 "BugBuddy Version: %s\n"
1500 "\n",
1501 distro,
1502 gnome_version->gnome_platform, gnome_version->gnome_date,
1503 gnome_version->gnome_distributor, VERSION);
1505 g_free (distro);
1507 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1508 buffer = gtk_text_view_get_buffer (text_view);
1509 gtk_text_buffer_get_end_iter (buffer, &end);
1510 gtk_text_buffer_insert (buffer, &end, version_info, strlen (version_info));
1512 g_free (version_info);
1515 static void
1516 fill_custom_info (BugzillaApplication *app, GtkBuilder *ui)
1518 GtkTextView *text_view;
1519 GtkTextIter end;
1520 GtkTextBuffer *buffer;
1521 gint status;
1522 gchar *output;
1523 gchar *standard_output = NULL;
1524 gchar *standard_error = NULL;
1525 GError *error = NULL;
1527 g_return_if_fail (app != NULL);
1528 g_return_if_fail (ui != NULL);
1530 /* fill the include info now that gdb has run */
1531 if (gopt_data.include_file != NULL) {
1532 fill_include_file (gopt_data.include_file, gopt_data.own_file, ui);
1535 if (app->extra_info_script == NULL) {
1536 return;
1539 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1540 buffer = gtk_text_view_get_buffer (text_view);
1541 gtk_text_buffer_get_end_iter (buffer, &end);
1543 if (!g_spawn_command_line_sync (app->extra_info_script, &standard_output, &standard_error,
1544 &status, &error)) {
1545 gchar *error_string = g_strdup_printf ("There was an error running \"%s\" script:\n"
1546 "%s", app->extra_info_script, error->message);
1547 gtk_text_buffer_insert (buffer, &end, error_string, strlen (error_string));
1549 g_free (error);
1550 g_free (error_string);
1551 return;
1554 output = g_strdup_printf ("Output of custom script \"%s\":\n"
1555 "%s\n\n",
1556 app->extra_info_script,
1557 standard_output ? standard_output : "");
1559 gtk_text_buffer_insert (buffer, &end, output, strlen (output));
1561 g_free (output);
1562 g_free (standard_output);
1563 g_free (standard_error);
1567 static void
1568 fill_proccess_info (pid_t pid, GtkBuilder *ui)
1570 GtkTextView *text_view;
1571 GtkTextIter end;
1572 GtkTextBuffer *buffer;
1573 char *mem;
1574 char *time;
1575 char *proccess_info;
1577 mem = proccess_get_mem_state (pid);
1578 time = proccess_get_time (pid);
1580 proccess_info = g_strdup_printf ("%s\n"
1581 "%s\n"
1582 "\n",
1583 mem, time);
1585 g_free (mem);
1586 g_free (time);
1588 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1589 buffer = gtk_text_view_get_buffer (text_view);
1590 gtk_text_buffer_get_end_iter (buffer, &end);
1591 gtk_text_buffer_insert (buffer, &end, proccess_info, strlen (proccess_info));
1593 g_free (proccess_info);
1597 static void
1598 fill_include_file (char *filename, gboolean own_file, GtkBuilder *ui)
1600 GtkTextView *text_view;
1601 GtkTextIter end;
1602 GtkTextBuffer *buffer;
1603 char *text;
1604 gboolean res;
1605 GError *error = NULL;
1607 res = g_file_get_contents (filename, &text, NULL, &error);
1609 /* if own_file is set, delete the file regardless of the result:
1610 * if _get_contents () fails chances are the file has already been
1611 * deleted anyway.
1614 if (own_file) {
1615 if (g_unlink (filename) == -1)
1616 g_warning ("Unable to delete %s", filename);
1619 if (!res) {
1620 buddy_error (NULL, error->message);
1621 g_error_free (error);
1622 return;
1625 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1626 buffer = gtk_text_view_get_buffer (text_view);
1627 gtk_text_buffer_get_end_iter (buffer, &end);
1628 gtk_text_buffer_insert (buffer, &end, text, strlen (text));
1630 g_free (text);
1634 /* copied from gconf-editor/gconf-util.c */
1636 static gchar *
1637 gconf_get_key_name_from_path (const gchar *path)
1639 const gchar *ptr;
1641 ptr = path + strlen (path);
1643 while (ptr[-1] != '/')
1644 ptr--;
1646 return g_strdup (ptr);
1649 static void
1650 fill_system_info (GtkBuilder *ui)
1652 GConfClient *gconf_client;
1653 GtkTextView *text_view;
1654 GtkTextIter end;
1655 GtkTextBuffer *buffer;
1656 GString *system_info, *modules;
1657 GSList *entries;
1658 struct utsname uts_buf;
1659 char *str;
1660 gboolean has_selinux, enforcing, a11y;
1662 g_return_if_fail (ui != NULL);
1664 system_info = g_string_new ("");
1665 modules = NULL;
1667 if (uname (&uts_buf) == 0) {
1668 g_string_append_printf (system_info, "System: %s %s %s %s\n", uts_buf.sysname, uts_buf.release, uts_buf.version, uts_buf.machine);
1671 /* X server checks */
1672 g_string_append_printf (system_info, "X Vendor: %s\n", ServerVendor (gdk_display_get_default()));
1673 g_string_append_printf (system_info, "X Vendor Release: %d\n", VendorRelease (gdk_display_get_default()));
1676 /* Selinux checks */
1677 has_selinux = FALSE;
1678 if (g_file_get_contents ("/proc/filesystems", &str, NULL, NULL)) {
1679 has_selinux = strstr (str, "selinuxfs") != NULL;
1680 g_free (str);
1682 if (has_selinux) {
1683 enforcing = TRUE;
1684 if (g_file_get_contents ("/selinux/enforce", &str, NULL, NULL)) {
1685 enforcing = strcmp (str, "0") != 0;
1686 g_free (str);
1688 g_string_append_printf (system_info, "Selinux: %s\n", enforcing?"Enforcing":"Permissive");
1689 } else {
1690 g_string_append_printf (system_info, "Selinux: No\n");
1693 /* A11y and gtk */
1694 gconf_client = gconf_client_get_default ();
1695 a11y = gconf_client_get_bool (gconf_client, ACCESSIBILITY_KEY, NULL);
1696 g_string_append_printf (system_info, "Accessibility: %s\n", a11y?"Enabled":"Disabled");
1697 str = gconf_client_get_string (gconf_client, GTK_THEME_KEY, NULL);
1698 g_string_append_printf (system_info, "GTK+ Theme: %s\n", str);
1699 g_free (str);
1700 str = gconf_client_get_string (gconf_client, ICON_THEME_KEY, NULL);
1701 g_string_append_printf (system_info, "Icon Theme: %s\n", str);
1702 g_free (str);
1704 /* add the GTK+ loaded modules. to do that, we look both in GConf and
1705 * inside the GTK_MODULES env var.
1708 entries = gconf_client_all_entries (gconf_client, GTK_MODULES_KEY, NULL);
1709 if (entries) {
1710 GSList *l;
1711 GConfEntry *entry;
1712 char *name;
1713 GConfValue *val;
1715 modules = g_string_new ("GTK+ Modules: ");
1717 for (l = entries; l != NULL; l = l->next) {
1718 entry = l->data;
1719 val = gconf_entry_get_value (entry);
1721 /* if the value is a boolean, check if it's activated, otherwise if it's
1722 * a string, it will refer to a boolean; check that.
1723 * i know this is quite ugly.
1725 if ((val->type == GCONF_VALUE_BOOL && gconf_value_get_bool (val)) ||
1726 (val->type == GCONF_VALUE_STRING && gconf_client_get_bool (gconf_client,
1727 gconf_value_get_string (val),
1728 NULL)))
1731 name = gconf_get_key_name_from_path (gconf_entry_get_key (entry));
1733 if (!g_strstr_len (modules->str, modules->len, name)) {
1734 g_string_append_printf (modules, "%s, ", name);
1737 g_free (name);
1740 gconf_entry_unref (entry);
1743 g_slist_free (entries);
1746 str = (char * ) g_getenv ("GTK_MODULES");
1747 if (str) {
1748 char **splitted;
1749 int i;
1751 if (!modules) {
1752 modules = g_string_new ("GTK+ Modules: ");
1755 /* modules are divided by G_SEARCHPATH_SEPARATOR */
1756 splitted = pango_split_file_list (str);
1758 for (i = 0; splitted[i]; i++) {
1759 if (!g_strstr_len (modules->str, modules->len, splitted[i])) {
1760 g_string_append_printf (modules, "%s, ", splitted[i]);
1764 g_strfreev (splitted);
1767 if (modules) {
1768 /* discard the last ", " */
1769 g_string_append_len (system_info, modules->str, (modules->len - 2));
1770 g_string_append (system_info, "\n");
1771 g_string_free (modules, TRUE);
1774 g_object_unref (gconf_client);
1776 g_string_append (system_info, "\n");
1778 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1779 buffer = gtk_text_view_get_buffer (text_view);
1780 gtk_text_buffer_get_end_iter (buffer, &end);
1781 gtk_text_buffer_insert (buffer, &end, system_info->str, system_info->len);
1783 g_string_free (system_info, TRUE);
1786 static void
1787 fill_stderr_info (GtkBuilder *ui)
1789 GtkTextView *text_view;
1790 GtkTextIter end;
1791 GtkTextBuffer *buffer;
1792 GString *stderr_info;
1793 char *str, *file;
1794 gchar **lines;
1795 int n_lines, i;
1797 g_return_if_fail (ui != NULL);
1799 stderr_info = g_string_new ("");
1801 /* .xsession-errors: read file */
1802 file = g_build_filename (g_get_home_dir (), ".xsession-errors", NULL);
1803 if (g_file_get_contents (file, &str, NULL, NULL)) {
1804 lines = g_strsplit (str, "\n", -1);
1805 g_free (str);
1806 n_lines = 0;
1807 while (lines[n_lines] != NULL) {
1808 n_lines++;
1811 if (n_lines > 0) {
1812 struct stat buf;
1813 char *mtime_age = NULL;
1814 time_t age = 0;
1817 if (stat (file, &buf) == 0) {
1818 age = time (NULL) - buf.st_mtime;
1819 if (age > 5) {
1820 mtime_age = g_strdup_printf (" (%d sec old)", (int) age);
1824 g_string_append_printf (stderr_info,
1825 "\n\n----------- .xsession-errors%s ---------------------\n",
1826 mtime_age?mtime_age:"");
1827 g_free (mtime_age);
1830 for (i = MAX (0, n_lines-16); i < n_lines; i++) {
1831 if (lines[i][0] != 0) {
1832 /* Limit line length to 200 chars to avoid excessive data */
1833 if (strlen (lines[i]) > 200) {
1834 lines[i][200] = 0;
1837 g_string_append_printf (stderr_info, "%s\n", lines[i]);
1840 if (n_lines > 0)
1841 g_string_append (stderr_info, "--------------------------------------------------\n");
1843 g_strfreev (lines);
1845 g_free (file);
1847 text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text"));
1848 buffer = gtk_text_view_get_buffer (text_view);
1849 gtk_text_buffer_get_end_iter (buffer, &end);
1850 gtk_text_buffer_insert (buffer, &end, stderr_info->str, stderr_info->len);
1852 g_string_free (stderr_info, TRUE);
1856 static gboolean
1857 has_gdb (void)
1859 gchar *gdb = g_find_program_in_path ("gdb");
1860 if (gdb) {
1861 g_free (gdb);
1862 return TRUE;
1865 return FALSE;
1868 static gboolean
1869 is_gnome_hacker (void)
1871 if (g_getenv ("GNOME_HACKER"))
1872 return TRUE;
1874 return FALSE;
1877 static gboolean
1878 run_gdb (const gchar *appname, pid_t pid)
1880 gchar *exec_str;
1881 gchar *title;
1882 gboolean res;
1883 GError *error = NULL;
1885 title = g_strdup_printf ("Debugging %s", appname);
1887 exec_str = g_strdup_printf ("gnome-terminal "
1888 "--title=\"%s\" "
1889 "--disable-factory "
1890 "--command=\"gdb %s %d\"",
1891 title, appname, (int) pid);
1892 g_free (title);
1893 res = g_spawn_command_line_sync (exec_str, NULL, NULL,
1894 NULL, &error);
1895 g_free (exec_str);
1896 if (!res) {
1897 g_warning ("Couldn't run debugger\n");
1898 return FALSE;
1901 return TRUE;
1905 main (int argc, char *argv[])
1907 gchar *s;
1908 BugzillaApplication *app;
1909 GnomeVersionInfo *gnome_version;
1910 guint progress;
1911 GtkWidget *main_window;
1912 GOptionContext *context;
1913 guint source_id;
1914 GError *err = NULL;
1915 GtkBuilder *ui = NULL;
1917 memset (&gopt_data, 0, sizeof (gopt_data));
1919 bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
1920 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1921 textdomain (GETTEXT_PACKAGE);
1923 context = g_option_context_new (N_("\n\nBug Buddy is a utility that helps report debugging\n"
1924 "information to the GNOME Bugzilla when a program crashes."));
1926 g_option_context_set_translation_domain (context, GETTEXT_PACKAGE);
1927 g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE);
1928 g_option_context_add_group (context, gtk_get_option_group (TRUE));
1930 if (!g_option_context_parse (context, &argc, &argv, &err)) {
1931 g_critical ("Failed to parse arguments: %s\n", err->message);
1932 g_error_free (err);
1933 g_option_context_free (context);
1934 exit (1);
1937 g_option_context_free (context);
1939 if (!bonobo_activation_is_initialized ())
1940 bonobo_activation_init (argc, argv);
1942 g_set_application_name (_("Bug Buddy"));
1943 gtk_window_set_default_icon_name ("bug-buddy");
1945 if (gopt_data.app_file && gopt_data.pid) {
1946 if (has_gdb () && is_gnome_hacker ()) {
1947 gboolean res;
1948 res = run_gdb (gopt_data.app_file, gopt_data.pid);
1949 if (!res)
1950 _exit (1);
1951 _exit(0);
1953 if (!has_gdb ()) {
1954 /* FIXME: Request PackageKit to install gdb */
1955 _exit(0);
1958 if (!elf_has_debug_symbols (gopt_data.pid)) {
1959 /* FIXME: Request PackageKit to install debug symbols packages */
1960 _exit(0);
1965 s = g_build_filename (BUDDY_DATADIR, "bug-buddy.gtkbuilder", NULL);
1967 ui = gtk_builder_new ();
1968 gtk_builder_add_from_file (ui, s, &err);
1969 gtk_builder_set_translation_domain (ui, GETTEXT_PACKAGE);
1971 g_free (s);
1973 if (!ui || err) {
1974 buddy_error (NULL,
1975 _("Bug Buddy could not load its user interface file.\n"
1976 "Please make sure Bug Buddy was installed correctly."));
1977 if (err) {
1978 g_error_free (err);
1980 return 0;
1983 main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window"));
1984 g_signal_connect (main_window, "delete-event", G_CALLBACK (delete_callback), ui);
1985 g_signal_connect (main_window, "key-press-event", G_CALLBACK (keypress_callback), ui);
1987 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
1989 progress = g_timeout_add (100, update_progress_bar,
1990 gtk_builder_get_object (ui, "progressbar"));
1991 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
1992 _("Collecting information from your system…"));
1994 if (gopt_data.app_file == NULL && gopt_data.package == NULL) {
1995 buddy_error (NULL, _("Either --appname or --package arguments are required.\n"));
1996 return 0;
1999 if (gopt_data.app_file && gopt_data.pid == 0 &&
2000 gopt_data.include_file == NULL) {
2001 buddy_error (NULL, _("Either --pid or --include arguments are required.\n"));
2002 return 0;
2005 if (gopt_data.own_file == TRUE && gopt_data.include_file == NULL) {
2006 buddy_error (NULL, _("The --unlink-tempfile option needs an --include argument.\n"));
2007 return 0;
2010 /* get some information about the gnome version */
2011 gnome_version = get_gnome_version_info ();
2012 if (gnome_version == NULL) {
2013 buddy_error (NULL, _("Bug Buddy was unable to retrieve information regarding "
2014 "the version of GNOME you are running. This is most likely "
2015 "due to a missing installation of gnome-desktop.\n"));
2016 return 0;
2019 g_object_set_data (G_OBJECT (ui), "gnome-version", gnome_version);
2021 /* connect the signal handler for the help button */
2022 g_signal_connect (gtk_builder_get_object (ui, "help-button"), "clicked",
2023 G_CALLBACK (help_callback), NULL);
2025 gtk_widget_show (main_window);
2027 apps = load_applications ();
2029 /* If we have a binary file it is a crash */
2030 if (gopt_data.app_file) {
2031 app = g_hash_table_lookup (apps, gopt_data.app_file);
2033 /* we handle an unknown application (no .desktop file) differently */
2034 if (app != NULL) {
2035 s = g_markup_printf_escaped (_("The %s application has crashed. "
2036 "We are collecting information about the crash to send to the "
2037 "developers in order to fix the problem."), app->name);
2038 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s);
2039 g_free (s);
2041 g_object_set_data (G_OBJECT (ui), "app", app);
2043 if (app->icon) {
2044 gtk_image_set_from_icon_name (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")),
2045 app->icon,
2046 GTK_ICON_SIZE_DIALOG);
2048 fill_gnome_info (app, gnome_version, ui);
2051 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")),
2052 _("Collecting information from the crash…"));
2054 fill_system_info (ui);
2056 fill_proccess_info (gopt_data.pid, ui);
2058 if (gopt_data.pid > 0) {
2059 /* again, if this is an unknown application, we connect a different callback that
2060 * will allow the user to save the trace rather than sending it to the GNOME Bugzilla */
2061 if (app == NULL) {
2062 source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui,
2063 gdb_finished_unknown_app, &err);
2064 } else {
2065 source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui, gdb_finished, &err);
2068 if (source_id == 0) {
2069 buddy_error (NULL, _("Bug Buddy encountered the following error when trying "
2070 "to retrieve debugging information: %s\n"), err->message);
2071 g_error_free (err);
2072 return 0;
2075 /* connect the close button callback so that we can remove the source from
2076 * the main loop (and kill gdb) if the user wants to quit before gdb is finished */
2077 g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (source_id));
2078 } else {
2079 if (app == NULL) {
2080 unknown_app_finished (ui);
2081 } else {
2082 known_app_finished (ui);
2085 g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_CRASH));
2086 } else {
2087 /* No binary file, so this is a non-crashing bug. Look the application from the --package arg */
2088 GtkWidget *email_entry;
2089 char *default_email;
2090 char *msg, *lang_note;
2091 const char *en_lang_note = N_("\n\nPlease write your report in English, if possible.");
2093 app = g_hash_table_find (apps, (GHRFunc)bugzilla_search_for_package, gopt_data.package);
2094 if (app == NULL) {
2095 /* Fallback to binary name */
2096 app = g_hash_table_lookup (apps, gopt_data.package);
2098 if (app == NULL) {
2099 buddy_error (NULL, _("Bug Buddy doesn't know how to send a suggestion for the application %s.\n"),
2100 gopt_data.package);
2101 return 0;
2104 g_object_set_data (G_OBJECT (ui), "app", app);
2106 if (app->icon) {
2107 gtk_image_set_from_icon_name (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")),
2108 app->icon,
2109 GTK_ICON_SIZE_DIALOG);
2111 fill_gnome_info (app, gnome_version, ui);
2112 fill_custom_info (app, ui);
2114 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box")));
2115 gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")));
2116 gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar")));
2118 lang_note = gettext (en_lang_note);
2119 msg = g_strconcat (_("Thank you for helping us to improve our software.\n"
2120 "Please fill in your suggestions/error information for %s application.\n\n"
2121 "A valid email address is required. This will allow developers to "
2122 "contact you for more information if necessary."),
2123 strcmp (lang_note, en_lang_note)? lang_note: NULL,
2124 NULL);
2125 s = g_markup_printf_escaped (msg, app->name);
2126 gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s);
2127 g_free (s);
2128 g_free (msg);
2130 s = g_markup_printf_escaped ("<span weight=\"bold\">%s</span>",
2131 _("Suggestion / Error description:"));
2132 gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "main-label")), s);
2133 g_free (s);
2135 show_pending_checkbox_if_pending (ui);
2136 g_signal_connect (gtk_builder_get_object (ui, "send-button"), "clicked",
2137 G_CALLBACK (on_send_clicked), ui);
2139 email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"));
2140 g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui);
2142 default_email = get_default_user_email ();
2144 if (default_email != NULL) {
2145 gtk_entry_set_text (GTK_ENTRY (email_entry), default_email);
2146 g_free (default_email);
2148 g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER (BUG_TYPE_REQUEST));
2151 g_signal_connect (gtk_builder_get_object (ui, "close-button"), "clicked",
2152 G_CALLBACK (close_callback), ui);
2154 gtk_main ();
2156 return 0;