Commit 9fdf3555 authored by Jehan's avatar Jehan

app: new error dialog to backtrace and encourage people to report bugs.

GIMP will now try to get a backtrace (on Unix machines only for now,
using g_on_error_stack_trace(); for Windows, we will likely have to look
into DrMinGW).
This is now applied to CRITICAL errors only, which usually means major
bugs but are currently mostly hidden unless you run GIMP in terminal. We
limit to 3 backtraces, because many CRITICAL typically get into domino
effect and cause more CRITICALs (for instance when a g_return*_if_fail()
returns too early).
parent 5da3bdb4
......@@ -159,9 +159,10 @@ gimp_show_message (Gimp *gimp,
GObject *handler,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message)
const gchar *message,
const gchar *trace)
{
const gchar *desc = "Message";
const gchar *desc = trace ? "Error" : "Message";
g_return_if_fail (GIMP_IS_GIMP (gimp));
g_return_if_fail (handler == NULL || G_IS_OBJECT (handler));
......@@ -174,8 +175,8 @@ gimp_show_message (Gimp *gimp,
{
if (gimp->gui.show_message)
{
gimp->gui.show_message (gimp, handler,
severity, domain, message);
gimp->gui.show_message (gimp, handler, severity,
domain, message, trace);
return;
}
else if (GIMP_IS_PROGRESS (handler) &&
......@@ -190,6 +191,8 @@ gimp_show_message (Gimp *gimp,
gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity,
NULL, NULL, &desc, NULL);
g_printerr ("%s-%s: %s\n\n", domain, desc, message);
if (trace)
g_printerr ("Back trace:\n%s\n\n", trace);
}
void
......
......@@ -35,7 +35,8 @@ struct _GimpGui
GObject *handler,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message);
const gchar *message,
const gchar *trace);
void (* help) (Gimp *gimp,
GimpProgress *progress,
const gchar *help_domain,
......@@ -144,7 +145,8 @@ void gimp_show_message (Gimp *gimp,
GObject *handler,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message);
const gchar *message,
const gchar *trace);
void gimp_help (Gimp *gimp,
GimpProgress *progress,
const gchar *help_domain,
......
......@@ -1123,7 +1123,7 @@ gimp_message_valist (Gimp *gimp,
message = g_strdup_vprintf (format, args);
gimp_show_message (gimp, handler, severity, NULL, message);
gimp_show_message (gimp, handler, severity, NULL, message, NULL);
g_free (message);
}
......@@ -1138,7 +1138,7 @@ gimp_message_literal (Gimp *gimp,
g_return_if_fail (handler == NULL || G_IS_OBJECT (handler));
g_return_if_fail (message != NULL);
gimp_show_message (gimp, handler, severity, NULL, message);
gimp_show_message (gimp, handler, severity, NULL, message, NULL);
}
void
......
......@@ -35,6 +35,7 @@
#include "widgets/gimpchanneltreeview.h"
#include "widgets/gimpcoloreditor.h"
#include "widgets/gimpcolormapeditor.h"
#include "widgets/gimpcriticaldialog.h"
#include "widgets/gimpdashboard.h"
#include "widgets/gimpdevicestatus.h"
#include "widgets/gimpdialogfactory.h"
......@@ -215,6 +216,15 @@ dialogs_error_get (GimpDialogFactory *factory,
return gimp_error_dialog_new (_("GIMP Message"));
}
GtkWidget *
dialogs_critical_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
gint view_size)
{
return gimp_critical_dialog_new (_("GIMP critical error"));
}
GtkWidget *
dialogs_close_all_get (GimpDialogFactory *factory,
GimpContext *context,
......
......@@ -77,6 +77,10 @@ GtkWidget * dialogs_error_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
gint view_size);
GtkWidget * dialogs_critical_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
gint view_size);
GtkWidget * dialogs_close_all_get (GimpDialogFactory *factory,
GimpContext *context,
GimpUIManager *ui_manager,
......
......@@ -286,6 +286,8 @@ static const GimpDialogFactoryEntry entries[] =
dialogs_action_search_get, TRUE, TRUE, TRUE),
TOPLEVEL ("gimp-error-dialog",
dialogs_error_get, TRUE, FALSE, FALSE),
TOPLEVEL ("gimp-critical-dialog",
dialogs_critical_get, TRUE, FALSE, FALSE),
TOPLEVEL ("gimp-close-all-dialog",
dialogs_close_all_get, TRUE, FALSE, FALSE),
TOPLEVEL ("gimp-quit-dialog",
......
......@@ -21,8 +21,11 @@
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
......@@ -41,6 +44,7 @@
#include <windows.h>
#endif
#define MAX_TRACES 3
/* private variables */
......@@ -52,23 +56,24 @@ static gchar *full_prog_name = NULL;
/* local function prototypes */
static G_GNUC_NORETURN void gimp_eek (const gchar *reason,
const gchar *message,
gboolean use_handler);
static void gimp_third_party_message_log_func (const gchar *log_domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data);
static void gimp_message_log_func (const gchar *log_domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data);
static void gimp_error_log_func (const gchar *domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data) G_GNUC_NORETURN;
static void gimp_third_party_message_log_func (const gchar *log_domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data);
static void gimp_message_log_func (const gchar *log_domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data);
static void gimp_error_log_func (const gchar *domain,
GLogLevelFlags flags,
const gchar *message,
gpointer data) G_GNUC_NORETURN;
static G_GNUC_NORETURN void gimp_eek (const gchar *reason,
const gchar *message,
gboolean use_handler);
static gchar * gimp_get_stack_trace (void);
/* public functions */
......@@ -129,7 +134,7 @@ errors_init (Gimp *gimp,
for (i = 0; i < G_N_ELEMENTS (log_domains); i++)
g_log_set_handler (log_domains[i],
G_LOG_LEVEL_MESSAGE,
G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_CRITICAL,
gimp_message_log_func, gimp);
g_log_set_handler ("GEGL",
......@@ -176,7 +181,7 @@ gimp_third_party_message_log_func (const gchar *log_domain,
* messages.
*/
gimp_show_message (gimp, NULL, GIMP_MESSAGE_WARNING,
log_domain, message);
log_domain, message, NULL);
}
else
{
......@@ -190,17 +195,45 @@ gimp_message_log_func (const gchar *log_domain,
const gchar *message,
gpointer data)
{
Gimp *gimp = data;
static gint n_traces;
GimpMessageSeverity severity = GIMP_MESSAGE_WARNING;
Gimp *gimp = data;
gchar *trace = NULL;
if (flags & G_LOG_LEVEL_CRITICAL)
{
severity = GIMP_MESSAGE_ERROR;
if (n_traces < MAX_TRACES)
{
/* Getting debug traces is time-expensive, and worse, some
* critical errors have the bad habit to create more errors
* (the first ones are therefore usually the most useful).
* This is why we keep track of how many times we made traces
* and stop doing them after a while.
* Hence when this happens, critical errors are simply processed as
* lower level errors.
*/
trace = gimp_get_stack_trace ();
n_traces++;
}
}
if (gimp)
{
gimp_show_message (gimp, NULL, GIMP_MESSAGE_WARNING, NULL, message);
gimp_show_message (gimp, NULL, severity,
NULL, message, trace);
}
else
{
g_printerr ("%s: %s\n\n",
gimp_filename_to_utf8 (full_prog_name), message);
if (trace)
g_printerr ("Back trace:\n%s\n\n", trace);
}
if (trace)
g_free (trace);
}
static void
......@@ -268,3 +301,91 @@ gimp_eek (const gchar *reason,
exit (EXIT_FAILURE);
}
static gchar *
gimp_get_stack_trace (void)
{
gchar *trace = NULL;
#if defined(G_OS_UNIX)
GString *gtrace = NULL;
gchar buffer[256];
ssize_t read_n;
pid_t pid;
int status;
int out_fd[2];
#endif
/* Though we should theoretically ask with GIMP_STACK_TRACE_QUERY, we
* just assume yes right now. TODO: improve this!
*/
if (stack_trace_mode == GIMP_STACK_TRACE_NEVER)
return NULL;
/* This works only on UNIX systems. On Windows, we'll have to find
* another method, probably with DrMingW.
*/
#if defined(G_OS_UNIX)
if (pipe (out_fd) == -1)
{
return NULL;
}
/* This is a trick to get the stack trace inside a string.
* GLib's g_on_error_stack_trace() unfortunately writes directly to
* the standard output, which is a very unfortunate implementation.
*/
pid = fork ();
if (pid == 0)
{
/* Child process. */
/* XXX I just don't understand why, but somehow the parent process
* doesn't get the output if I don't print something first. I just
* leave this very dirty hack until I figure out what's going on.
*/
printf(" ");
/* Redirect the debugger output. */
dup2 (out_fd[1], STDOUT_FILENO);
close (out_fd[0]);
close (out_fd[1]);
g_on_error_stack_trace (full_prog_name);
_exit (0);
}
else if (pid > 0)
{
/* Main process. */
waitpid (pid, &status, 0);
}
else if (pid == (pid_t) -1)
{
/* No trace can be done. */
return NULL;
}
gtrace = g_string_new ("");
/* It is important to close the writing side of the pipe, otherwise
* the read() will wait forever without getting the information that
* writing is finished.
*/
close (out_fd[1]);
while ((read_n = read (out_fd[0], buffer, 256)) > 0)
{
g_string_append_len (gtrace, buffer, read_n);
}
close (out_fd[0]);
if (gtrace)
trace = g_string_free (gtrace, FALSE);
if (trace && strlen (g_strstrip (trace)) == 0)
{
/* Empty strings are the same as no strings. */
g_free (trace);
trace = NULL;
}
#endif
return trace;
}
......@@ -34,6 +34,7 @@
#include "plug-in/gimpplugin.h"
#include "widgets/gimpcriticaldialog.h"
#include "widgets/gimpdialogfactory.h"
#include "widgets/gimpdockable.h"
#include "widgets/gimperrorconsole.h"
......@@ -55,6 +56,7 @@ typedef struct
Gimp *gimp;
gchar *domain;
gchar *message;
gchar *trace;
GObject *handler;
GimpMessageSeverity severity;
} GimpLogMessageData;
......@@ -64,13 +66,19 @@ static gboolean gui_message_idle (gpointer user_data);
static gboolean gui_message_error_console (Gimp *gimp,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message);
const gchar *message,
const gchar *trace);
static gboolean gui_message_error_dialog (Gimp *gimp,
GObject *handler,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message);
const gchar *message,
const gchar *trace);
static void gui_message_console (GimpMessageSeverity severity,
const gchar *domain,
const gchar *message,
const gchar *trace);
static gchar * gui_message_format (GimpMessageSeverity severity,
const gchar *domain,
const gchar *message);
......@@ -80,12 +88,13 @@ gui_message (Gimp *gimp,
GObject *handler,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message)
const gchar *message,
const gchar *trace)
{
switch (gimp->message_handler)
{
case GIMP_ERROR_CONSOLE:
if (gui_message_error_console (gimp, severity, domain, message))
if (gui_message_error_console (gimp, severity, domain, message, trace))
return;
gimp->message_handler = GIMP_MESSAGE_BOX;
......@@ -104,6 +113,7 @@ gui_message (Gimp *gimp,
data->gimp = gimp;
data->domain = g_strdup (domain);
data->message = g_strdup (message);
data->trace = trace ? g_strdup (trace) : NULL;
data->handler = handler? g_object_ref (handler) : NULL;
data->severity = severity;
......@@ -112,14 +122,15 @@ gui_message (Gimp *gimp,
data, g_free);
return;
}
if (gui_message_error_dialog (gimp, handler, severity, domain, message))
if (gui_message_error_dialog (gimp, handler, severity,
domain, message, trace))
return;
gimp->message_handler = GIMP_CONSOLE;
/* fallthru */
case GIMP_CONSOLE:
gui_message_console (severity, domain, message);
gui_message_console (severity, domain, message, trace);
break;
}
}
......@@ -133,14 +144,18 @@ gui_message_idle (gpointer user_data)
data->handler,
data->severity,
data->domain,
data->message))
data->message,
data->trace))
{
gui_message_console (data->severity,
data->domain,
data->message);
data->message,
data->trace);
}
g_free (data->domain);
g_free (data->message);
if (data->trace)
g_free (data->trace);
if (data->handler)
g_object_unref (data->handler);
......@@ -151,10 +166,13 @@ static gboolean
gui_message_error_console (Gimp *gimp,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message)
const gchar *message,
const gchar *trace)
{
GtkWidget *dockable;
/* TODO: right now the error console does nothing with the trace. */
dockable = gimp_dialog_factory_find_widget (gimp_dialog_factory_get_singleton (),
"gimp-error-console");
......@@ -255,16 +273,59 @@ global_error_dialog (void)
FALSE);
}
static GtkWidget *
global_critical_dialog (void)
{
GdkScreen *screen;
gint monitor;
monitor = gimp_get_monitor_at_pointer (&screen);
return gimp_dialog_factory_dialog_new (gimp_dialog_factory_get_singleton (),
screen, monitor,
NULL /*ui_manager*/,
"gimp-critical-dialog", -1,
FALSE);
}
static gboolean
gui_message_error_dialog (Gimp *gimp,
GObject *handler,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message)
const gchar *message,
const gchar *trace)
{
GtkWidget *dialog;
GtkWidget *dialog;
GtkMessageType type = GTK_MESSAGE_ERROR;
switch (severity)
{
case GIMP_MESSAGE_INFO: type = GTK_MESSAGE_INFO; break;
case GIMP_MESSAGE_WARNING: type = GTK_MESSAGE_WARNING; break;
case GIMP_MESSAGE_ERROR: type = GTK_MESSAGE_ERROR; break;
}
if (trace)
{
/* Process differently when traces are included.
* The reason is that this will take significant place, and cannot
* be processed as a progress message or in the global dialog. It
* will require its own dedicated dialog which will encourage
* people to report the bug.
*/
gchar *text;
if (GIMP_IS_PROGRESS (handler))
dialog = global_critical_dialog ();
text = gui_message_format (severity, domain, message);
gimp_critical_dialog_add (GIMP_CRITICAL_DIALOG (dialog),
text, trace);
g_free (text);
gtk_widget_show (dialog);
return TRUE;
}
else if (GIMP_IS_PROGRESS (handler))
{
/* If there's already an error dialog associated with this
* progress, then continue without trying gimp_progress_message().
......@@ -278,15 +339,7 @@ gui_message_error_dialog (Gimp *gimp,
}
else if (GTK_IS_WIDGET (handler))
{
GtkWidget *parent = GTK_WIDGET (handler);
GtkMessageType type = GTK_MESSAGE_ERROR;
switch (severity)
{
case GIMP_MESSAGE_INFO: type = GTK_MESSAGE_INFO; break;
case GIMP_MESSAGE_WARNING: type = GTK_MESSAGE_WARNING; break;
case GIMP_MESSAGE_ERROR: type = GTK_MESSAGE_ERROR; break;
}
GtkWidget *parent = GTK_WIDGET (handler);
dialog =
gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (parent)),
......@@ -324,11 +377,31 @@ gui_message_error_dialog (Gimp *gimp,
static void
gui_message_console (GimpMessageSeverity severity,
const gchar *domain,
const gchar *message)
const gchar *message,
const gchar *trace)
{
gchar *formatted_message;
formatted_message = gui_message_format (severity, domain, message);
g_printerr ("%s\n\n", formatted_message);
g_free (formatted_message);
if (trace)
g_printerr ("Stack trace:\n%s\n\n", trace);
}
static gchar *
gui_message_format (GimpMessageSeverity severity,
const gchar *domain,
const gchar *message)
{
const gchar *desc = "Message";
gchar *formatted_message;
gimp_enum_get_value (GIMP_TYPE_MESSAGE_SEVERITY, severity,
NULL, NULL, &desc, NULL);
g_printerr ("%s-%s: %s\n\n", domain, desc, message);
formatted_message = g_strdup_printf ("%s-%s: %s", domain, desc, message);
return formatted_message;
}
......@@ -23,7 +23,8 @@ void gui_message (Gimp *gimp,
GObject *handler,
GimpMessageSeverity severity,
const gchar *domain,
const gchar *message);
const gchar *message,
const gchar *trace);
#endif /* __GUI_VTABLE_H__ */
......@@ -61,7 +61,7 @@ message_invoker (GimpProcedure *procedure,
if (gimp->plug_in_manager->current_plug_in)
domain = gimp_plug_in_get_undo_desc (gimp->plug_in_manager->current_plug_in);
gimp_show_message (gimp, G_OBJECT (progress), GIMP_MESSAGE_WARNING,
domain, message);
domain, message, NULL);
}
return gimp_procedure_get_return_values (procedure, success,
......
......@@ -38,15 +38,17 @@
#include "gimp-intl.h"
static void
gimp_show_library_version (const gchar *package,
gint build_time_major,
gint build_time_minor,
gint build_time_micro,
gint run_time_major,
gint run_time_minor,
gint run_time_micro)
static gchar *
gimp_library_version (const gchar *package,
gint build_time_major,
gint build_time_minor,
gint build_time_micro,
gint run_time_major,
gint run_time_minor,
gint run_time_micro,
gboolean localized)
{
gchar *lib_version;
gchar *build_time_version;
gchar *run_time_version;
......@@ -60,91 +62,156 @@ gimp_show_library_version (const gchar *package,
run_time_micro);
/* show versions of libraries used by GIMP */
g_print (_("using %s version %s (compiled against version %s)"),
package, run_time_version, build_time_version);
g_print ("\n");
lib_version = g_strdup_printf (localized ?
_("using %s version %s (compiled against version %s)") :
"using %s version %s (compiled against version %s)",
package, run_time_version, build_time_version);
g_free (run_time_version);
g_free (build_time_version);
return lib_version;
}
static void
gimp_show_library_versions (void)
static gchar *
gimp_library_versions (gboolean localized)
{
gint gegl_major_version;
gint gegl_minor_version;
gint gegl_micro_version;
gchar *lib_versions;
gchar *lib_version;
gchar *temp;
gint gegl_major_version;
gint gegl_minor_version;
gint gegl_micro_version;
gegl_get_version (&gegl_major_version,
&gegl_minor_version,
&gegl_micro_version);
gimp_show_library_version ("GEGL",
GEGL_MAJOR_VERSION,
GEGL_MINOR_VERSION,
GEGL_MICRO_VERSION,
gegl_major_version,
gegl_minor_version,
gegl_micro_version);
gimp_show_library_version ("GLib",
GLIB_MAJOR_VERSION,
GLIB_MINOR_VERSION,
GLIB_MICRO_VERSION,
glib_major_version,
glib_minor_version,
glib_micro_version);
gimp_show_library_version ("GdkPixbuf",
GDK_PIXBUF_MAJOR,
GDK_PIXBUF_MINOR,
GDK_PIXBUF_MICRO,
gdk_pixbuf_major_version,
gdk_pixbuf_minor_version,
gdk_pixbuf_micro_version);
lib_versions = gimp_library_version ("GEGL",
GEGL_MAJOR_VERSION,
GEGL_MINOR_VERSION,
GEGL_MICRO_VERSION,
gegl_major_version,
gegl_minor_version,
gegl_micro_version,
localized);
lib_version = gimp_library_version ("GLib",
GLIB_MAJOR_VERSION,
GLIB_MINOR_VERSION,
GLIB_MICRO_VERSION,
glib_major_version,
glib_minor_version,
glib_micro_version,
localized);
temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version);
g_free (lib_versions);
g_free (lib_version);
lib_versions = temp;
lib_version = gimp_library_version ("GdkPixbuf",
GDK_PIXBUF_MAJOR,
GDK_PIXBUF_MINOR,
GDK_PIXBUF_MICRO,
gdk_pixbuf_major_version,
gdk_pixbuf_minor_version,
gdk_pixbuf_micro_version,
localized);
temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version);
g_free (lib_versions);
g_free (lib_version);
lib_versions = temp;
#ifndef GIMP_CONSOLE_COMPILATION
gimp_show_library_version ("GTK+",
GTK_MAJOR_VERSION,
GTK_MINOR_VERSION,
GTK_MICRO_VERSION,
gtk_major_version,
gtk_minor_version,
gtk_micro_version);
lib_version = gimp_library_version ("GTK+",
GTK_MAJOR_VERSION,
GTK_MINOR_VERSION,
GTK_MICRO_VERSION,
gtk_major_version,
gtk_minor_version,
gtk_micro_version,
localized);
temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version);
g_free (lib_versions);
g_free (lib_version);
lib_versions = temp;
#endif
gimp_show_library_version ("Pango",
PANGO_VERSION_MAJOR,
PANGO_VERSION_MINOR,
PANGO_VERSION_MICRO,
pango_version () / 100 / 100,
pango_version () / 100 % 100,
pango_version () % 100);
gimp_show_library_version ("Fontconfig",
FC_MAJOR, FC_MINOR, FC_REVISION,
FcGetVersion () / 100 / 100,
FcGetVersion () / 100 % 100,
FcGetVersion () % 100);
g_print (_("using %s version %s (compiled against version %s)"),
"Cairo", cairo_version_string (), CAIRO_VERSION_STRING);
g_print ("\n");
lib_version = gimp_library_version ("Pango",
PANGO_VERSION_MAJOR,
PANGO_VERSION_MINOR,
PANGO_VERSION_MICRO,
pango_version () / 100 / 100,
pango_version () / 100 % 100,
pango_version () % 100,
localized);
temp = g_strdup_printf ("%s\n%s", lib_versions, lib_version);
g_free (lib_versions);
g_free (lib_version);
lib_versions = temp;