/* bug-buddy bug submitting program
*
* Copyright (C) 1999 - 2001 Jacob Berkman
* Copyright 2000, 2001 Ximian, Inc.
*
* Author: jacob berkman <jacob@bug-buddy.org>
*
* 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 <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/utsname.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <glib/gi18n.h>
#include <gnome.h>
#include <libgnomeui/gnome-window-icon.h>
#include <libgnomevfs/gnome-vfs-utils.h>
#include <gtk/gtkbuilder.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <libgnomecanvas/gnome-canvas-pixbuf.h>
#include <libgnome/libgnometypebuiltins.h>
#include <gdk/gdkx.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <gconf/gconf-client.h>
#ifdef HAVE_NETWORKMANAGER
#include <libnm_glib.h>
#endif
#include <libsoup/soup.h>
#include <libsoup/soup-xmlrpc-message.h>
#include <sys/types.h>
#include <signal.h>
#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,
_("<span weight=\"bold\">Network Connection Error</span>\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 : "<empty backtrace>");
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 ("<small><i><span weight=\"bold\">%s</span> %s</i></small>",
_("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 ("<span weight=\"bold\">%s</span>",
_("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;
}
syntax highlighted by Code2HTML, v. 0.9.1