/* bug-buddy bug submitting program * * Copyright (C) 1999 - 2001 Jacob Berkman * Copyright 2000, 2001 Ximian, Inc. * * Author: jacob berkman * * This program is free software; you can redistribute it and/or * modify it under the terms of version 2 of the GNU General Public * License as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include "config.h" #include "eds-buddy.h" #include "gdb-buddy.h" #include "bugzilla.h" #include "bug-buddy.h" #include "distribution.h" #include "proccess.h" #include "forbidden-words.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_NETWORKMANAGER #include #endif #include #include #include #include #define d(x) #define USE_PROXY_KEY "/system/http_proxy/use_http_proxy" #define PROXY_HOST_KEY "/system/http_proxy/host" #define PROXY_PORT_KEY "/system/http_proxy/port" #define USE_PROXY_AUTH "/system/http_proxy/use_authentication" #define PROXY_USER "/system/http_proxy/authentication_user" #define PROXY_PASSWORD "/system/http_proxy/authentication_password" #define ACCESSIBILITY_KEY "/desktop/gnome/interface/accessibility" #define GTK_THEME_KEY "/desktop/gnome/interface/gtk_theme" #define ICON_THEME_KEY "/desktop/gnome/interface/icon_theme" #define DESKTOP_IS_HOME_DIR "/apps/nautilus/preferences/desktop_is_home_dir" static GOptionData gopt_data; static int bug_count = 0; static GHashTable *apps = NULL; static const GOptionEntry options[] = { { "name", '\0', 0, G_OPTION_ARG_STRING, &gopt_data.name, N_("Name of contact"), N_("NAME") }, { "email", '\0', 0, G_OPTION_ARG_STRING, &gopt_data.email, N_("Email address of contact"), N_("EMAIL") }, { "package", '\0', 0, G_OPTION_ARG_STRING, &gopt_data.package, N_("Package containing the program"), N_("PACKAGE") }, { "package-ver",'\0', 0, G_OPTION_ARG_STRING, &gopt_data.package_ver, N_("Version of the package"), N_("VERSION") }, { "appname", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.app_file, N_("File name of crashed program"), N_("FILE") }, { "pid", '\0', 0, G_OPTION_ARG_INT, &gopt_data.pid, N_("PID of crashed program"), N_("PID") }, { "core", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.core_file, N_("Core file from program"), N_("FILE") }, { "include", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.include_file, N_("Text file to include in the report"), N_("FILE") }, { "minidump", '\0', 0, G_OPTION_ARG_FILENAME, &gopt_data.minidump_file, N_("MiniDump file with info about the crash"), N_("FILE") }, { "kill", '\0', 0, G_OPTION_ARG_INT, &gopt_data.kill, N_("PID of the program to kill after the report"), N_("KILL") }, { NULL } }; enum { NETWORK_CONNECTED, NETWORK_DISCONNECTED, NETWORK_UNKNOWN }; static void fill_stderr_info (GtkBuilder *ui); static void fill_custom_info (BugzillaApplication *app, GtkBuilder *ui); static void close_callback (GtkWidget *widget, gpointer user_data); static void bug_buddy_quit (GtkBuilder *ui); static void buddy_error (GtkWidget *parent, const char *msg, ...) { GtkWidget *w; GtkDialog *d; gchar *s; va_list args; /* No va_list version of dialog_new, construct the string ourselves. */ va_start (args, msg); s = g_strdup_vprintf (msg, args); va_end (args); w = gtk_message_dialog_new (GTK_WINDOW (parent), 0, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, "%s", s); d = GTK_DIALOG (w); gtk_dialog_set_default_response (d, GTK_RESPONSE_OK); gtk_dialog_run (d); gtk_widget_destroy (w); g_free (s); } static void lock_text (GtkBuilder *ui) { GtkTextView *text_view; GtkTextBuffer *buffer; GtkTextIter start; GtkTextIter end; static GtkTextTag *tag = NULL; char *text; text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_get_end_iter (buffer, &end); text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); if (!tag) { GtkStyle *style = gtk_widget_get_style (GTK_WIDGET (text_view)); tag = gtk_text_buffer_create_tag (buffer, "lock_tag", "editable", FALSE, /* I don't like how it looks like dimming also fg "foreground-gdk", &style->fg[GTK_STATE_INSENSITIVE], */ "background-gdk", &style->bg[GTK_STATE_INSENSITIVE], NULL); } if (gtk_text_iter_forward_search (&start, "Backtrace was generated from", GTK_TEXT_SEARCH_TEXT_ONLY, NULL, &end, NULL)) { gtk_text_iter_forward_line (&end); gtk_text_buffer_apply_tag_by_name (buffer, "lock_tag", &start, &end); } } static gboolean search_forbidden_words (GtkBuilder *ui) { GtkTextView *text_view; GtkTextBuffer *buffer; int i; gboolean found = FALSE; static GtkTextTag *tag = NULL; text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); if (!tag) { tag = gtk_text_buffer_create_tag (buffer, "forbbiden_tag", "foreground", "white", "background", "blue", NULL); } for (i = 0; forbidden_words[i]; i++) { GtkTextIter start; GtkTextIter end; gtk_text_buffer_get_start_iter (buffer, &start); while (gtk_text_iter_forward_search (&start, forbidden_words[i], GTK_TEXT_SEARCH_TEXT_ONLY, &start, &end, NULL)) { gtk_text_buffer_apply_tag_by_name (buffer, "forbbiden_tag", &start, &end); start = end; found = TRUE; } } return found; } static void copy_review (GtkWidget *button, gpointer data) { GtkTextView *text_view; GtkTextBuffer *buffer; GtkTextIter start; GtkTextIter end; GtkBuilder *ui = (GtkBuilder*) data; text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_select_range (buffer, &start, &end); gtk_text_buffer_copy_clipboard (buffer, gtk_clipboard_get (GDK_NONE)); } static void edit_review (GtkWidget *button, gpointer data) { GtkTextView *text_view; GtkBuilder *ui = (GtkBuilder*) data; gboolean editable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); gtk_text_view_set_editable (text_view, editable); if (editable) { lock_text (ui); } } static void close_review (GtkWidget *button, gpointer data) { GtkWidget *review_dialog = GTK_WIDGET (data); gtk_widget_hide (review_dialog); } static gboolean delete_review (GtkWidget *widget, GdkEvent *event, gpointer user_data) { gtk_widget_hide (widget); return TRUE; /* don't destroy */ } static void show_review (GtkWidget *button, gpointer data) { GtkWidget *review_dialog, *main_window; GtkWidget *edit, *copy, *close; GtkBuilder *ui = (GtkBuilder*) data; static gboolean initialized = FALSE; if (!initialized) { review_dialog = GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog")); main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window")); copy = GTK_WIDGET (gtk_builder_get_object (ui, "copy-review-button")); edit = GTK_WIDGET (gtk_builder_get_object (ui, "edit-review-button")); close = GTK_WIDGET (gtk_builder_get_object (ui, "close-review-button")); gtk_window_set_transient_for (GTK_WINDOW (review_dialog), GTK_WINDOW (main_window)); g_signal_connect (G_OBJECT (copy), "clicked", G_CALLBACK (copy_review), ui); g_signal_connect (G_OBJECT (edit), "toggled", G_CALLBACK (edit_review), ui); g_signal_connect (G_OBJECT (close), "clicked", G_CALLBACK (close_review), review_dialog); g_signal_connect (G_OBJECT (review_dialog), "delete-event", G_CALLBACK (delete_review), NULL); initialized = TRUE; } lock_text (ui); gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-dialog"))); } static GnomeVersionInfo* get_gnome_version_info (void) { GnomeVersionInfo *version; xmlDoc *doc; char *xml_file; xmlNode *node; guchar *platform, *minor, *micro, *distributor, *date; version = g_new0 (GnomeVersionInfo, 1); xml_file = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_DATADIR, "gnome-about/gnome-version.xml", TRUE, NULL); if (!xml_file) return NULL; doc = xmlParseFile (xml_file); g_free (xml_file); if (!doc) return NULL; platform = minor = micro = distributor = date = NULL; for (node = xmlDocGetRootElement (doc)->children; node; node = node->next) { if (!strcmp ((char *)node->name, "platform")) platform = xmlNodeGetContent (node); else if (!strcmp ((char *)node->name, "minor")) minor = xmlNodeGetContent (node); else if (!strcmp ((char *)node->name, "micro")) micro = xmlNodeGetContent (node); else if (!strcmp ((char *)node->name, "distributor")) distributor = xmlNodeGetContent (node); else if (!strcmp ((char *)node->name, "date")) date = xmlNodeGetContent (node); } if (platform && minor && micro) version->gnome_platform = g_strdup_printf ("%s.%s.%s", platform, minor, micro); if (distributor && *distributor) version->gnome_distributor = g_strdup ((char *)distributor); if (date && *date) version->gnome_date = g_strdup ((char *)date); xmlFree (platform); xmlFree (minor); xmlFree (micro); xmlFree (distributor); xmlFree (date); xmlFreeDoc (doc); return version; } static gboolean update_progress_bar (gpointer data) { GtkProgressBar *pbar = GTK_PROGRESS_BAR (data); gtk_progress_bar_pulse (pbar); return TRUE; } static void save_email (const char *email) { GConfClient *conf_client; conf_client = gconf_client_get_default (); gconf_client_set_string (conf_client, "/apps/bug-buddy/email_address", email, NULL); g_object_unref (conf_client); } static void link_callback (GtkLinkButton *button, gpointer user_data) { const gchar *link = gtk_link_button_get_uri (button); if (gnome_vfs_url_show (link) != GNOME_VFS_OK) { char *text; text = g_markup_printf_escaped (_("Bug Buddy was unable to view the link \"%s\"\n"), link); buddy_error (NULL, text); g_free (text); } return; } static void save_to_file (const gchar *filename, const gchar *text) { GError *error = NULL; if (!g_file_set_contents (filename, text, -1, &error)) { g_warning ("Unable to save document %s: %s\n", filename, error->message); g_error_free (error); } } static void network_error (SoupMessage *msg, GtkBuilder *ui) { GtkWidget *dialog; int res; gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar"))); dialog = gtk_message_dialog_new_with_markup (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, _("Network Connection Error\n" "Maybe no Network Connection available.\n" "Do you want to store this report until " "a Network Connection is available?")); res = gtk_dialog_run (GTK_DIALOG (dialog)); gtk_widget_destroy (dialog); if (res == GTK_RESPONSE_YES) { gchar *dirname; gchar *filename; xmlChar *message_string; dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ()); if (!g_file_test (dirname, G_FILE_TEST_IS_DIR)) { g_mkdir_with_parents (dirname, 0755); } filename = g_strdup_printf ("%s/%ld", dirname, (long)time (NULL)); message_string = soup_xmlrpc_message_to_string (SOUP_XMLRPC_MESSAGE (msg)); save_to_file (filename, (const gchar*) message_string); xmlFree (message_string); g_free (dirname); g_free (filename); } bug_buddy_quit (ui); return; } static void remove_pending_reports (void) { GDir *dir; char *dirname; GError *error = NULL; dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ()); dir = g_dir_open (dirname, 0, &error); if (dir) { const char *name = g_dir_read_name (dir); while (name) { char *path = g_strdup_printf ("%s/%s", dirname, name); g_remove (path); g_free (path); name = g_dir_read_name (dir); } g_dir_close (dir); } g_remove (dirname); g_free (dirname); } static void all_sent (GtkBuilder *ui) { GtkWidget *close_button; /* hide the progressbar */ gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar"))); close_button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button")); gtk_widget_show (close_button); } static void previous_sent (SoupMessage *msg, GtkBuilder *ui) { if (--bug_count == 0) { all_sent (ui); } } static void bug_sent (SoupMessage *msg, GtkBuilder *ui) { GtkWidget *button; GtkWidget *image; char *text = NULL; char *errmsg = NULL; char *str = NULL; char *ptr = NULL; long bugid; char *response; char *tmp; GtkWidget *urlbutton; GtkRequisition requisition; GError *err = NULL; button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button")); gtk_button_set_label (GTK_BUTTON (button), _("_Close")); gtk_button_set_use_underline (GTK_BUTTON (button), TRUE); image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON), gtk_button_set_image (GTK_BUTTON (button), image); if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) { network_error (msg, ui); } else { remove_pending_reports (); } /* parse the XML-RPC response */ response = bugzilla_parse_response (msg, &err); if (response != NULL) { bugid = strtol (response, &tmp, 10); GtkWidget *main_vbox; /* we need a reference to the vbox containing the text so that we * can add a GtkLinkButton to the bug report */ main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox")); if (response == tmp) { char *url; url = strstr (response, "ViewURL="); if (url) url += strlen ("ViewURL="); else url = ""; text = g_strdup (url); } else text = g_strdup_printf ("http://bugzilla.gnome.org/show_bug.cgi?id=%ld", bugid); /* create a clickable link to the bug report */ urlbutton = gtk_link_button_new (text); g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL); gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0); gtk_widget_show (urlbutton); g_free (text); text = g_markup_printf_escaped (_("A bug report detailing your software crash has been sent to GNOME. " "This information will allow the developers to understand the cause " "of the crash and prepare a solution for it.\n\n" "You may be contacted by a GNOME developer if more details are " "required about the crash.\n\n" "You can view your bug report and follow its progress with this URL:\n")) ; gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), text); g_free (text); save_email (gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry")))); } else { errmsg = _("Bug Buddy has encountered an error while submitting your report " "to the Bugzilla server. Details of the error are included below.\n\n"); if (err != NULL) { switch (err->code) { case BUGZILLA_ERROR_RECV_BAD_STATUS: text = g_strdup_printf (_("Server returned bad state. This is most likely a server " "issue and should be reported to bugmaster@gnome.org\n\n%s"), err->message); break; case BUGZILLA_ERROR_RECV_PARSE_FAILED: text = g_strdup_printf (_("Failed to parse the xml-rpc response. Response follows:\n\n%s"), err->message); break; case BUGZILLA_ERROR_RECV_FAULT: /* in this case, the error message returned is the faultCode and faultString from * the XML-RPC response, separated by a colon. We construct our error message * based on the faultString */ ptr = strstr (err->message, ":"); if (ptr == NULL) { text = g_strdup_printf (_("Bugzilla reported an error when trying to process your " "request, but was unable to parse the response.")); break; } /* skip the colon */ ptr++; /* see http://cvs.gnome.org/viewcvs/bugzilla-newer/Bugzilla/RPC.pm?view=markup */ if (g_str_equal (ptr, "invalid_username")) { text = g_strdup_printf (_("The email address you provided is not valid.")); } else if (g_str_equal (ptr, "account_disabled")) { text = g_strdup_printf (_("The account associated with the email address " "provided has been disabled.")); } else if (g_str_equal (ptr, "product_doesnt_exist")) { text = g_strdup_printf (_("The product specified doesn't exist or has been " "renamed. Please upgrade to the latest version.")); } else if (g_str_equal (ptr, "component_not_valid")) { text = g_strdup_printf (_("The component specified doesn't exist or has been " "renamed. Please upgrade to the latest version.")); } else if (g_str_equal (ptr, "require_summary")) { text = g_strdup_printf (_("The summary is required in your bug report. " "This should not happen with the latest Bug Buddy.")); } else if (g_str_equal (ptr, "description_required")) { text = g_strdup_printf (_("The description is required in your bug report. " "This should not happen with the latest Bug Buddy.")); } else { text = g_strdup_printf (_("The fault code returned by Bugzilla is not recognized. " "Please report the following information to " "bugzilla.gnome.org manually:\n\n%s"), err->message); } break; default: text = g_strdup_printf (_("An unknown error occurred. This is most likely a problem with " "bug-buddy. Please report this problem manually at bugzilla." "gnome.org\n\n")); break; } str = g_strconcat (errmsg, text, NULL); gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), str); g_free (str); g_free (text); g_error_free (err); } } if (--bug_count == 0) { all_sent (ui); } gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui, "main-window")), &requisition); gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui, "main-window")), requisition.width, requisition.height); g_free (response); } static void set_proxy (SoupSession *session) { GConfClient *gconf_client; char *host; int port; char *proxy_uri; SoupUri *uri; char *username = NULL; char *password = NULL; gconf_client = gconf_client_get_default (); if (gconf_client_get_bool (gconf_client, USE_PROXY_KEY, NULL) == FALSE) { g_object_unref (gconf_client); return; } host = gconf_client_get_string (gconf_client, PROXY_HOST_KEY, NULL); if (host == NULL) { g_object_unref (gconf_client); return; } port = gconf_client_get_int (gconf_client, PROXY_PORT_KEY, NULL); if (port == 0) port = 80; if (gconf_client_get_bool (gconf_client, USE_PROXY_AUTH, NULL)) { username = gconf_client_get_string (gconf_client, PROXY_USER, NULL); password = gconf_client_get_string (gconf_client, PROXY_PASSWORD, NULL); } if (username && password) proxy_uri = g_strdup_printf ("http://%s:%s@%s:%d", username, password, host, port); else proxy_uri = g_strdup_printf ("http://%s:%d", host, port); uri = soup_uri_new (proxy_uri); g_object_set (G_OBJECT (session), "proxy-uri", uri, NULL); g_free (host); g_free (username); g_free (password); g_free (proxy_uri); soup_uri_free (uri); g_object_unref (gconf_client); } static char* create_report_title (BugzillaApplication *app, int type, const char *description) { char *title; long size = 0; char *tmp = NULL; if (description) { tmp = g_malloc0 (256); /* This should be safe enough for 24 UTF-8 chars. * anyway, I miss a g_utf8_strndup :) */ size = g_utf8_strlen (description, -1); if (size > 24) { g_utf8_strncpy (tmp, description, 24); } else { g_utf8_strncpy (tmp, description, size); } } if (type == BUG_TYPE_CRASH) { title = g_strdup_printf ("crash in %s: %s%s", app->cname, tmp ? tmp : "empty description", (tmp && size > 24) ? "..." : ""); } else { title = g_strdup_printf ("%s: %s%s", app->cname, tmp ? tmp : "empty description", (tmp && size > 24) ? "..." : ""); } g_free (tmp); return title; } static void send_report (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui) { GtkTextView *text_view; GtkTextBuffer *buffer; GtkTextIter start; GtkTextIter end; int type; char *gdb_text; char *details_text; char *title; char *final_text; const char *email; SoupSession *session; SoupXmlrpcMessage *message; GError *err = NULL; gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check"))); text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_get_end_iter (buffer, &end); gdb_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "details-view")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_get_end_iter (buffer, &end); details_text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); type = GPOINTER_TO_INT (g_object_get_data (G_OBJECT(ui), "type")); final_text = g_strdup_printf ("%s%s\n\n\n%s", type == BUG_TYPE_CRASH ? "What were you doing when the application crashed?\n" : "", details_text != NULL ? details_text : "", gdb_text != NULL ? gdb_text : ""); email = gtk_entry_get_text (GTK_ENTRY (gtk_builder_get_object (ui, "email-entry"))); title = create_report_title (app, type, details_text); message = bugzilla_create_report (app, type, gnome_version, email, title, final_text, ui, gopt_data.minidump_file, &err); if (message == NULL) { char *text; if (err != NULL) { text = g_strdup_printf (_("Unable to create the bug report: %s\n"), err->message); } else { text = g_strdup_printf (_("There was an error creating the bug report\n")); } buddy_error (NULL, text); g_free (text); g_free (gdb_text); g_free (details_text); g_free (title); g_free (final_text); bug_buddy_quit (ui); return; } session = soup_session_async_new (); set_proxy (session); if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gtk_builder_get_object (ui, "pending-reports-check")))) { GDir *dir; char *dirname; GError *error = NULL; dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ()); dir = g_dir_open (dirname, 0, &error); if (dir) { const char *name = g_dir_read_name (dir); while (name != NULL) { char *path; char *contents; path = g_strdup_printf ("%s/%s", dirname, name); if (g_file_get_contents (path, &contents, NULL, NULL)) { SoupXmlrpcMessage *msg; msg = soup_xmlrpc_message_new ("http://bugzilla.gnome.org/bugbuddy.cgi"); soup_xmlrpc_message_from_string (msg, contents); bug_count++; soup_xmlrpc_message_persist (msg); soup_session_queue_message (session, SOUP_MESSAGE (msg), (SoupMessageCallbackFn)previous_sent, ui); g_free (contents); } g_free (path); name = g_dir_read_name (dir); } g_dir_close (dir); } } bug_count++; soup_xmlrpc_message_persist (message); soup_session_queue_message (session, SOUP_MESSAGE (message), (SoupMessageCallbackFn)bug_sent, ui); g_free (gdb_text); g_free (details_text); g_free (title); g_free (final_text); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")), _("Sending...")); } /* A local part is valid if it is one or more valid characters. */ static gboolean email_local_part_is_valid (const char *local_part) { const char *character; if (!local_part[0]) return FALSE; for (character = local_part; *character; character++) { /* RFC 3696 says *any* printable ASCII character can * appear in local-part, subject to quoting rules. */ if (g_ascii_isprint (*character)) continue; /* Not valid character, not valid local part. */ return FALSE; } return TRUE; } /* A domain label is valid if it is one or more valid characters. */ static gboolean email_domain_label_is_valid (const char *domain_label) { const char *character; int i; /* Validate each character, whilst measuring length, i. */ for (i = 0; *(character = domain_label + i); i++) { /* If character is alphanumeric it is valid. */ if (g_ascii_isalnum (*character)) continue; /* If it's a hyphen, it's also valid. */ if (*character == '-') continue; /* Anything else is invalid */ return FALSE; } /* Labels must be between 1 and 63 characters long */ if (i < 1 || i > 63) { return FALSE; } return TRUE; } /* A domain is valid if it is one or more valid, dot-separated labels. */ static gboolean email_domain_is_valid (const char *domain) { char **labels; char **this_label; /* If there is no domain, there are no domain labels and the domain is * not valid. */ if (!domain[0]) return FALSE; /* Split the domain on the dot to validate labels. */ labels = g_strsplit (domain, ".", 0); for (this_label = labels; *this_label; this_label++) { if (!email_domain_label_is_valid (*this_label)) { g_strfreev (labels); return FALSE; } } g_strfreev (labels); return TRUE; } /* Check for *simple* email addresses of the form user@host, with checks * in characters used, and sanity checks on the form of host. */ /* FIXME: Should we provide a useful error message? */ static gboolean email_is_valid (const char *address) { char *local_part; char *domain; char *at_sign; gboolean is_valid; /* Split on the *last* '@' character: */ at_sign = strrchr (address, '@'); if (at_sign == NULL) return FALSE; local_part = g_strndup (address, at_sign - address); domain = g_strdup (at_sign + 1); /* Check each part is valid */ is_valid = email_local_part_is_valid (local_part) && email_domain_is_valid (domain); g_free (local_part); g_free (domain); return is_valid; } static void check_email (GtkEditable *editable, gpointer data) { const char *email; GtkBuilder *ui = (GtkBuilder*) data; email = gtk_entry_get_text (GTK_ENTRY (editable)); gtk_widget_set_sensitive (GTK_WIDGET (gtk_builder_get_object (ui, "send-button")), email_is_valid (email)); } static void on_send_clicked (GtkWidget *button, gpointer data) { BugzillaApplication *app; GnomeVersionInfo *gnome_version; GtkRequisition requisition; GtkBuilder *ui = (GtkBuilder*) data; app = g_object_get_data (G_OBJECT (ui), "app"); gnome_version = g_object_get_data (G_OBJECT (ui), "gnome-version"); /* hide the send button immediately so that the user can't click * it more than once (this will create multiple bugs) */ gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "send-button"))); gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar"))); gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box"))); gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "review-box"))); gtk_widget_size_request (GTK_WIDGET (gtk_builder_get_object (ui, "main-window")), &requisition); gtk_window_resize (GTK_WINDOW (gtk_builder_get_object (ui, "main-window")), requisition.width, requisition.height); send_report (app, gnome_version, ui); } static gboolean gdb_insert_text (const gchar *stacktrace, GtkBuilder *ui) { GtkTextView *text_view; GtkTextIter end; GtkTextBuffer *buffer; /* FIXME: These strings are gdb specific, we should add here also dbx */ const char *bt_step1 = "#1"; const char *bt_step2 = "#2"; const char *bt_step3 = "#3"; if (!g_strrstr (stacktrace, bt_step1) && !g_strrstr (stacktrace, bt_step2) && !g_strrstr (stacktrace, bt_step3)) { return FALSE; } text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_end_iter (buffer, &end); /* add the stacktrace to the GtkTextView */ gtk_text_buffer_insert (buffer, &end, stacktrace, strlen (stacktrace)); return TRUE; } static void show_pending_checkbox_if_pending (GtkBuilder *ui) { char *dirname; GtkWidget *check; dirname = g_strdup_printf ("%s/.gnome2/bug-buddy/pending_reports", g_get_home_dir ()); if (g_file_test (dirname, G_FILE_TEST_IS_DIR)) { check = GTK_WIDGET (gtk_builder_get_object (ui, "pending-reports-check")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE); gtk_widget_show (check); } g_free (dirname); } static GtkWidget* create_debuginfo_link (void) { GtkWidget *urlbutton; /* create a clickable link to the bug report */ urlbutton = gtk_link_button_new_with_label ("http://live.gnome.org/GettingTraces/DistroSpecificInstructions", /* Translators: This is the hyperlink which takes to http://live.gnome.org/GettingTraces/DistroSpecificInstructions * page. Please also mention that the page is in English */ _("Getting useful crash reports")); g_signal_connect (G_OBJECT (urlbutton), "clicked", G_CALLBACK (link_callback), NULL); return urlbutton; } static void add_minidump_text_output (const char *minidump_file, GtkBuilder *ui) { GtkTextView *text_view; GtkTextIter end; GtkTextBuffer *buffer; gchar *command_line; gchar *standard_output, *standard_error; gint exit_status; GError *error; text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_end_iter (buffer, &end); command_line = g_strdup_printf ("minidump_dump %s", minidump_file); if (g_spawn_command_line_sync (command_line, &standard_output, &standard_error, &exit_status, &error)) { gtk_text_buffer_insert (buffer, &end, standard_output, strlen (standard_output)); g_free (standard_output); g_free (standard_error); } else { g_error_free (error); } } static void useless_finished (GtkBuilder *ui) { GtkWidget *button, *image, *main_vbox, *urlbutton; BugzillaApplication *app; char *label_text; app = g_object_get_data (G_OBJECT (ui), "app"); gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar"))); label_text = g_markup_printf_escaped (_("The application %s crashed. The bug reporting tool was " "unable to collect enough information about the crash to be " "useful to the developers.\n\n" "In order to submit useful reports, please consider installing " "debug packages for your distribution.\n" "Click the link below to get information about how to install " "these packages:\n"), app->name); gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), label_text); main_vbox = GTK_WIDGET (gtk_builder_get_object (ui, "main-vbox")); urlbutton = create_debuginfo_link (); gtk_box_pack_end (GTK_BOX (main_vbox), urlbutton, FALSE, FALSE, 0); gtk_widget_show (urlbutton); g_free (label_text); button = GTK_WIDGET (gtk_builder_get_object (ui, "close-button")); gtk_button_set_label (GTK_BUTTON (button), _("_Close")); gtk_button_set_use_underline (GTK_BUTTON (button), TRUE); image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_BUTTON), gtk_button_set_image (GTK_BUTTON (button), image); gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "email-entry"))); } static void known_app_finished (GtkBuilder *ui) { BugzillaApplication *app; GtkWidget *email_entry; GtkWidget *button; char *default_email; char *lang_note, *label_text, *s; const char *en_lang_note = N_("\n\nPlease write your report in English, if possible."); app = g_object_get_data (G_OBJECT (ui), "app"); fill_custom_info (app, ui); fill_stderr_info (ui); gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box"))); gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button"))); gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar"))); lang_note = gettext (en_lang_note); label_text = g_strconcat (_("Information about the %s application crash has been successfully collected. " "Please provide some more details about what you were doing when " "the application crashed.\n\n" "A valid email address is required. This will allow the developers to " "contact you for more information if necessary."), strcmp (lang_note, en_lang_note) ? lang_note : NULL, NULL); s = g_markup_printf_escaped (label_text, app->name); gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s); g_free (s); g_free (label_text); show_pending_checkbox_if_pending (ui); button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button")); g_signal_connect (button, "clicked", G_CALLBACK (on_send_clicked), ui); email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry")); g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui); default_email = get_default_user_email (); if (default_email != NULL) { gtk_entry_set_text (GTK_ENTRY (email_entry), default_email); g_free (default_email); } else { gtk_widget_set_sensitive (button, FALSE); } if (search_forbidden_words (ui)) { char *review_text = g_markup_printf_escaped ("%s %s", _("WARNING:"), _("Some sensitive data is likely present in the crash details. " "Please review and edit the information if you are concerned " "about transmitting passwords or other sensitive data.")); gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "review-label")), review_text); } g_signal_connect (gtk_builder_get_object (ui, "review-button"), "clicked", G_CALLBACK (show_review), ui); gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "review-box"))); gtk_widget_grab_focus (GTK_WIDGET (gtk_builder_get_object (ui, "details-view"))); } static void gdb_finished (const gchar *stacktrace, gpointer data) { GtkBuilder *ui = (GtkBuilder*) data; if (gdb_insert_text (stacktrace, ui)) { known_app_finished (ui); } else { useless_finished (ui); } } static void on_save_clicked (GtkWidget *button, gpointer user_data) { GtkBuilder *ui = (GtkBuilder *)user_data; GtkWidget *dialog; const char *desktop; char *filename; gboolean desktop_is_home_dir, saved; GConfClient *gconf_client; saved = FALSE; dialog = gtk_file_chooser_dialog_new (_("Save File"), GTK_WINDOW (gtk_builder_get_object (ui, "main-window")), GTK_FILE_CHOOSER_ACTION_SAVE, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, NULL); gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); gconf_client = gconf_client_get_default (); desktop_is_home_dir = gconf_client_get_bool (gconf_client, DESKTOP_IS_HOME_DIR, NULL); g_object_unref (gconf_client); if (desktop_is_home_dir) desktop = g_get_home_dir(); else desktop = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), desktop); filename = g_strconcat (gopt_data.app_file, _("-bugreport.txt"), NULL); gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), filename); g_free (filename); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) { char *filename; GtkTextView *text_view; GtkTextBuffer *buffer; GtkTextIter start; GtkTextIter end; gchar *text; text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_start_iter (buffer, &start); gtk_text_buffer_get_end_iter (buffer, &end); text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); save_to_file (filename, text); g_free (filename); g_free (text); saved = TRUE; } gtk_widget_destroy (dialog); if (saved) { bug_buddy_quit (ui); } } static void focus_details (GtkWidget *widget, gpointer data) { gtk_widget_grab_focus (widget); } static void unknown_app_finished (GtkBuilder *ui) { GtkWidget *button; char *label_text; fill_stderr_info (ui); if (gopt_data.minidump_file) { add_minidump_text_output (gopt_data.minidump_file, ui); } /* don't need user input, so hide these widgets */ gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box"))); gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar"))); /* make the send button into a save button :-) */ button = GTK_WIDGET (gtk_builder_get_object (ui, "send-button")); gtk_button_set_label (GTK_BUTTON (button), _("_Save Bug Report")); gtk_button_set_use_underline (GTK_BUTTON (button), TRUE); g_signal_connect (GTK_BUTTON (button), "clicked", G_CALLBACK (on_save_clicked), ui); gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button"))); label_text = g_markup_printf_escaped (_("The application %s has crashed.\n" "Information about the crash has been successfully collected.\n\n" "This application is not known to bug-buddy, therefore the " "bug report cannot be sent to the GNOME Bugzilla. Please save the " "bug to a text file and report it to the appropriate bug tracker " "for this application."), gopt_data.app_file); gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), label_text); /* FIXME: If we just grab the focus here to the GtkTextView it will crash on the blink_cb because * the window is nop mapped! Is this a gtk+ bug? are we doing something wrong? * Let's do a funny Workaround: */ g_signal_connect_after (gtk_builder_get_object (ui, "details-view"), "realize", G_CALLBACK (focus_details), NULL); gtk_widget_realize (GTK_WIDGET (gtk_builder_get_object (ui, "details-view"))); } static void gdb_finished_unknown_app (const gchar *stacktrace, gpointer data) { GtkBuilder *ui = (GtkBuilder*) data; gdb_insert_text (stacktrace, ui); unknown_app_finished (ui); } static void bug_buddy_quit (GtkBuilder *ui) { gpointer data; g_return_if_fail (ui != NULL); data = g_object_get_data (G_OBJECT (ui), "sourceid"); if (data != NULL) { guint source_id = GPOINTER_TO_UINT (data); /* removes the context from the main loop and kills any remaining * gdb process */ if (source_id > 0) { g_source_remove (source_id); g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (0)); } } g_hash_table_destroy (apps); g_object_unref (ui); gtk_main_quit (); } static void close_callback (GtkWidget *widget, gpointer user_data) { GtkBuilder *ui = (GtkBuilder *)user_data; bug_buddy_quit (ui); } static void help_callback (GtkWidget *widget, gpointer user_data) { GnomeProgram *program = (GnomeProgram *)user_data; GError *error = NULL; gnome_help_display_desktop (program, "user-guide", "user-guide", "feedback-bugs", &error); if (error) { GtkWidget *error_dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, _("There was an error displaying help: %s"), error->message); g_signal_connect (G_OBJECT (error_dialog), "response", G_CALLBACK (gtk_widget_destroy), NULL); gtk_window_set_resizable (GTK_WINDOW (error_dialog), FALSE); gtk_widget_show (error_dialog); g_error_free (error); error = NULL; } } static gboolean delete_callback (GtkWidget *widget, GdkEvent *event, gpointer data) { close_callback (NULL, data); return TRUE; } static void fill_gnome_info (BugzillaApplication *app, GnomeVersionInfo *gnome_version, GtkBuilder *ui) { char *version_info; char *distro; GtkTextView *text_view; GtkTextIter end; GtkTextBuffer *buffer; g_return_if_fail (app != NULL); g_return_if_fail (gnome_version != NULL); g_return_if_fail (ui != NULL); distro = get_distro_name (); version_info = g_strdup_printf ("Distribution: %s\n" "Gnome Release: %s %s (%s)\n" "BugBuddy Version: %s\n" "\n", distro, gnome_version->gnome_platform, gnome_version->gnome_date, gnome_version->gnome_distributor, VERSION); g_free (distro); text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_insert (buffer, &end, version_info, strlen (version_info)); g_free (version_info); } static void fill_custom_info (BugzillaApplication *app, GtkBuilder *ui) { GtkTextView *text_view; GtkTextIter end; GtkTextBuffer *buffer; gint status; gchar *output; gchar *standard_output = NULL; gchar *standard_error = NULL; GError *error = NULL; g_return_if_fail (app != NULL); g_return_if_fail (ui != NULL); if (app->extra_info_script == NULL) { return; } text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_end_iter (buffer, &end); if (!g_spawn_command_line_sync (app->extra_info_script, &standard_output, &standard_error, &status, &error)) { gchar *error_string = g_strdup_printf ("There was an error running \"%s\" script:\n" "%s", app->extra_info_script, error->message); gtk_text_buffer_insert (buffer, &end, error_string, strlen (error_string)); g_free (error); g_free (error_string); return; } output = g_strdup_printf ("Output of custom script \"%s\":\n" "%s\n\n", app->extra_info_script, standard_output ? standard_output : ""); gtk_text_buffer_insert (buffer, &end, output, strlen (output)); g_free (output); g_free (standard_output); g_free (standard_error); } static void fill_proccess_info (pid_t pid, GtkBuilder *ui) { GtkTextView *text_view; GtkTextIter end; GtkTextBuffer *buffer; char *mem; char *time; char *proccess_info; mem = proccess_get_mem_state (pid); time = proccess_get_time (pid); proccess_info = g_strdup_printf ("%s\n" "%s\n" "\n", mem, time); g_free (mem); g_free (time); text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_insert (buffer, &end, proccess_info, strlen (proccess_info)); g_free (proccess_info); } static void fill_include_file (char *filename, GtkBuilder *ui) { GtkTextView *text_view; GtkTextIter end; GtkTextBuffer *buffer; char *text; GError *error = NULL; if (g_file_get_contents (filename, &text, NULL, &error) != TRUE) { buddy_error (NULL, error->message); g_error_free (error); return; } text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_insert (buffer, &end, text, strlen (text)); g_free (text); } static void fill_system_info (GtkBuilder *ui) { GConfClient *gconf_client; GtkTextView *text_view; GtkTextIter end; GtkTextBuffer *buffer; GString *system_info; struct utsname uts_buf; char *str; gboolean has_selinux, enforcing, a11y; g_return_if_fail (ui != NULL); system_info = g_string_new (""); if (uname (&uts_buf) == 0) { g_string_append_printf (system_info, "System: %s %s %s %s\n", uts_buf.sysname, uts_buf.release, uts_buf.version, uts_buf.machine); } /* X server checks */ g_string_append_printf (system_info, "X Vendor: %s\n", ServerVendor (gdk_display)); g_string_append_printf (system_info, "X Vendor Release: %d\n", VendorRelease (gdk_display)); /* Selinux checks */ has_selinux = FALSE; if (g_file_get_contents ("/proc/filesystems", &str, NULL, NULL)) { has_selinux = strstr (str, "selinuxfs") != NULL; g_free (str); } if (has_selinux) { enforcing = TRUE; if (g_file_get_contents ("/selinux/enforce", &str, NULL, NULL)) { enforcing = strcmp (str, "0") != 0; g_free (str); } g_string_append_printf (system_info, "Selinux: %s\n", enforcing?"Enforcing":"Permissive"); } else { g_string_append_printf (system_info, "Selinux: No\n"); } /* A11y and gtk */ gconf_client = gconf_client_get_default (); a11y = gconf_client_get_bool (gconf_client, ACCESSIBILITY_KEY, NULL); g_string_append_printf (system_info, "Accessibility: %s\n", a11y?"Enabled":"Disabled"); str = gconf_client_get_string (gconf_client, GTK_THEME_KEY, NULL); g_string_append_printf (system_info, "GTK+ Theme: %s\n", str); g_free (str); str = gconf_client_get_string (gconf_client, ICON_THEME_KEY, NULL); g_string_append_printf (system_info, "Icon Theme: %s\n", str); g_free (str); g_object_unref (gconf_client); g_string_append (system_info, "\n"); text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_insert (buffer, &end, system_info->str, system_info->len); g_string_free (system_info, TRUE); } static void fill_stderr_info (GtkBuilder *ui) { GtkTextView *text_view; GtkTextIter end; GtkTextBuffer *buffer; GString *stderr_info; char *str, *file; gchar **lines; int n_lines, i; g_return_if_fail (ui != NULL); stderr_info = g_string_new (""); /* .xsession-errors: read file */ file = g_build_filename (g_get_home_dir (), ".xsession-errors", NULL); if (g_file_get_contents (file, &str, NULL, NULL)) { lines = g_strsplit (str, "\n", -1); g_free (str); n_lines = 0; while (lines[n_lines] != NULL) { n_lines++; } if (n_lines > 0) { struct stat buf; char *mtime_age = NULL; time_t age = 0; if (stat (file, &buf) == 0) { age = time (NULL) - buf.st_mtime; if (age > 5) { mtime_age = g_strdup_printf (" (%d sec old)", (int) age); } } g_string_append_printf (stderr_info, "\n\n----------- .xsession-errors%s ---------------------\n", mtime_age?mtime_age:""); g_free (mtime_age); } for (i = MAX (0, n_lines-16); i < n_lines; i++) { if (lines[i][0] != 0) { /* Limit line length to 200 chars to avoid excessive data */ if (strlen (lines[i]) > 200) { lines[i][200] = 0; } g_string_append_printf (stderr_info, "%s\n", lines[i]); } } if (n_lines > 0) g_string_append (stderr_info, "--------------------------------------------------\n"); g_strfreev (lines); } g_free (file); text_view = GTK_TEXT_VIEW (gtk_builder_get_object (ui, "gdb-text")); buffer = gtk_text_view_get_buffer (text_view); gtk_text_buffer_get_end_iter (buffer, &end); gtk_text_buffer_insert (buffer, &end, stderr_info->str, stderr_info->len); g_string_free (stderr_info, TRUE); } static GdkPixbuf* load_icon (const char *icon) { GdkPixbuf *pixbuf = NULL; if (g_path_is_absolute (icon)) { pixbuf = gdk_pixbuf_new_from_file_at_size (icon, 48, 48, NULL); } if (pixbuf == NULL) { pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), icon, 48, 0, NULL); } if (pixbuf == NULL && strrchr (icon, '.') != NULL) { char *name; name = g_strndup (icon, strlen (icon) - strlen (strrchr (icon, '.'))); pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), name, 48, 0, NULL); g_free (name); } if (pixbuf == NULL) { char *filename; filename = g_strdup_printf (BUDDY_ICONDIR"/pixmaps/%s", icon); pixbuf = gdk_pixbuf_new_from_file_at_size (filename, 48, 48, NULL); g_free (filename); } return pixbuf; } int main (int argc, char *argv[]) { gchar *s; BugzillaApplication *app; GnomeVersionInfo *gnome_version; guint progress; GtkWidget *main_window; GOptionContext *context; GnomeProgram *program; guint source_id; GError *err = NULL; GtkBuilder *ui = NULL; memset (&gopt_data, 0, sizeof (gopt_data)); bindtextdomain (PACKAGE, GNOMELOCALEDIR); bind_textdomain_codeset (PACKAGE, "UTF-8"); textdomain (PACKAGE); context = g_option_context_new (N_("\n\nBug Buddy is a utility that helps report debugging\n" "information to the GNOME Bugzilla when a program crashes.")); g_option_context_set_translation_domain (context, GETTEXT_PACKAGE); g_option_context_add_main_entries (context, options, GETTEXT_PACKAGE); program = gnome_program_init (PACKAGE, VERSION, LIBGNOMEUI_MODULE, argc, argv, GNOME_PARAM_GOPTION_CONTEXT, context, GNOME_PARAM_APP_DATADIR, BUDDY_DATADIR, GNOME_CLIENT_PARAM_SM_CONNECT, FALSE, NULL); g_set_application_name (_("Bug Buddy")); gtk_window_set_default_icon_name ("bug-buddy"); s = gnome_program_locate_file (NULL, GNOME_FILE_DOMAIN_APP_DATADIR, "bug-buddy.gtkbuilder", TRUE, NULL); if (s) { ui = gtk_builder_new (); gtk_builder_add_from_file (ui, s, &err); gtk_builder_set_translation_domain (ui, GETTEXT_PACKAGE); } if (!ui) { buddy_error (NULL, _("Bug Buddy could not load its user interface file.\n" "Please make sure Bug Buddy was installed correctly.")); g_object_unref (program); return 0; } g_free (s); main_window = GTK_WIDGET (gtk_builder_get_object (ui, "main-window")); g_signal_connect (main_window, "delete-event", G_CALLBACK (delete_callback), ui); gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "final-box"))); progress = g_timeout_add (100, update_progress_bar, gtk_builder_get_object (ui, "progressbar")); gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")), _("Collecting information from your system...")); if (gopt_data.app_file == NULL && gopt_data.package == NULL) { buddy_error (NULL, _("Either --appname or --package arguments are required.\n")); g_object_unref (program); return 0; } if (gopt_data.app_file && gopt_data.pid == 0 && gopt_data.include_file == NULL && gopt_data.minidump_file == NULL) { buddy_error (NULL, _("Either --pid , --include or --minidump arguments are required.\n")); g_object_unref (program); return 0; } /* get some information about the gnome version */ gnome_version = get_gnome_version_info (); if (gnome_version == NULL) { buddy_error (NULL, _("Bug Buddy was unable to retrieve information regarding " "the version of GNOME you are running. This is most likely " "due to a missing installation of gnome-desktop.\n")); g_object_unref (program); return 0; } g_object_set_data (G_OBJECT (ui), "gnome-version", gnome_version); /* connect the signal handler for the help button */ g_signal_connect (gtk_builder_get_object (ui, "help-button"), "clicked", G_CALLBACK (help_callback), program); gtk_widget_show (main_window); apps = load_applications (); /* If we have a binary file it is a crash */ if (gopt_data.app_file) { app = g_hash_table_lookup (apps, gopt_data.app_file); /* we handle an unknown application (no .desktop file) differently */ if (app != NULL) { s = g_markup_printf_escaped (_("The %s application has crashed. " "We are collecting information about the crash to send to the " "developers in order to fix the problem."), app->name); gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s); g_free (s); g_object_set_data (G_OBJECT (ui), "app", app); if (app->icon) { GdkPixbuf *pixbuf; pixbuf = load_icon (app->icon); if (pixbuf) { gtk_image_set_from_pixbuf (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")), pixbuf); g_object_unref(pixbuf); } } fill_gnome_info (app, gnome_version, ui); } gtk_progress_bar_set_text (GTK_PROGRESS_BAR (gtk_builder_get_object (ui, "progressbar")), _("Collecting information from the crash...")); fill_system_info (ui); fill_proccess_info (gopt_data.pid, ui); if (gopt_data.pid > 0) { /* again, if this is an unknown application, we connect a different callback that * will allow the user to save the trace rather than sending it to the GNOME Bugzilla */ if (app == NULL) { source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui, gdb_finished_unknown_app, &err); } else { source_id = gdb_get_trace (gopt_data.app_file, gopt_data.pid, ui, gdb_finished, &err); } if (source_id == 0) { buddy_error (NULL, _("Bug Buddy encountered the following error when trying " "to retrieve debugging information: %s\n"), err->message); g_error_free (err); g_object_unref (program); return 0; } /* connect the close button callback so that we can remove the source from * the main loop (and kill gdb) if the user wants to quit before gdb is finished */ g_object_set_data (G_OBJECT (ui), "sourceid", GUINT_TO_POINTER (source_id)); } else { if (app == NULL) { unknown_app_finished (ui); } else { known_app_finished (ui); } } g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_CRASH)); } else { /* No binary file, so this is a non-crashing bug. Look the application from the --package arg */ GtkWidget *email_entry; char *default_email; char *msg, *lang_note; const char *en_lang_note = N_("\n\nPlease write your report in English, if possible."); app = g_hash_table_find (apps, (GHRFunc)bugzilla_search_for_package, gopt_data.package); if (app == NULL) { /* Fallback to binary name */ app = g_hash_table_lookup (apps, gopt_data.package); } if (app == NULL) { buddy_error (NULL, _("Bug Buddy doesn't know how to send a suggestion for the application %s.\n"), gopt_data.package); return 0; } g_object_set_data (G_OBJECT (ui), "app", app); if (app->icon) { GdkPixbuf *pixbuf; pixbuf = load_icon (app->icon); if (pixbuf) { gtk_image_set_from_pixbuf (GTK_IMAGE (gtk_builder_get_object (ui, "app-image")), pixbuf); g_object_unref (pixbuf); } } fill_gnome_info (app, gnome_version, ui); fill_custom_info (app, ui); gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "final-box"))); gtk_widget_show (GTK_WIDGET (gtk_builder_get_object (ui, "send-button"))); gtk_widget_hide (GTK_WIDGET (gtk_builder_get_object (ui, "progressbar"))); lang_note = gettext (en_lang_note); msg = g_strconcat (_("Thank you for helping us improving our software.\n" "Please fill your suggestions/error information for %s application.\n\n" "A valid email address is required. This will allow developers to " "contact you for more information if necessary."), strcmp (lang_note, en_lang_note)? lang_note: NULL, NULL); s = g_markup_printf_escaped (msg, app->name); gtk_label_set_text (GTK_LABEL (gtk_builder_get_object (ui, "main-text")), s); g_free (s); g_free (msg); s = g_markup_printf_escaped ("%s", _("Suggestion / Error description:")); gtk_label_set_markup (GTK_LABEL (gtk_builder_get_object (ui, "main-label")), s); g_free (s); show_pending_checkbox_if_pending (ui); g_signal_connect (gtk_builder_get_object (ui, "send-button"), "clicked", G_CALLBACK (on_send_clicked), ui); email_entry = GTK_WIDGET (gtk_builder_get_object (ui, "email-entry")); g_signal_connect (email_entry, "changed", G_CALLBACK (check_email), ui); default_email = get_default_user_email (); if (default_email != NULL) { gtk_entry_set_text (GTK_ENTRY (email_entry), default_email); g_free (default_email); } g_object_set_data (G_OBJECT (ui), "type", GINT_TO_POINTER(BUG_TYPE_REQUEST)); } if (gopt_data.include_file != NULL) { fill_include_file (gopt_data.include_file, ui); } g_signal_connect (gtk_builder_get_object (ui, "close-button"), "clicked", G_CALLBACK (close_callback), ui); gtk_main (); g_object_unref (program); return 0; }