1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include <dlfcn.h>
7 #include <fcntl.h>
8 #include <glib.h>
9 #include <gtk/gtk.h>
10 #include <string.h>
11 
12 #include <cctype>
13 
14 #include "crashreporter.h"
15 #include "crashreporter_gtk_common.h"
16 
17 #define LABEL_MAX_CHAR_WIDTH 48
18 
19 using std::ios;
20 using std::string;
21 using std::vector;
22 
23 using namespace CrashReporter;
24 
25 static GtkWidget* gViewReportButton = 0;
26 static GtkWidget* gCommentTextLabel = 0;
27 static GtkWidget* gCommentText = 0;
28 
29 static bool gCommentFieldHint = true;
30 
31 // handle from dlopen'ing libgnome
32 static void* gnomeLib = nullptr;
33 // handle from dlopen'ing libgnomeui
34 static void* gnomeuiLib = nullptr;
35 
LoadSettings()36 static void LoadSettings() {
37   /*
38    * NOTE! This code needs to stay in sync with the preference checking
39    *       code in in nsExceptionHandler.cpp.
40    */
41 
42   StringTable settings;
43   if (ReadStringsFromFile(gSettingsPath + "/" + kIniFile, settings, true)) {
44     if (settings.find("IncludeURL") != settings.end() &&
45         gIncludeURLCheck != 0) {
46       gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck),
47                                    settings["IncludeURL"][0] != '0');
48     }
49     bool enabled = true;
50     if (settings.find("SubmitReport") != settings.end())
51       enabled = settings["SubmitReport"][0] != '0';
52     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck),
53                                  enabled);
54   }
55 }
56 
Escape(const string & str)57 static string Escape(const string& str) {
58   string ret;
59   for (auto c : str) {
60     if (c == '\\') {
61       ret += "\\\\";
62     } else if (c == '\n') {
63       ret += "\\n";
64     } else if (c == '\t') {
65       ret += "\\t";
66     } else {
67       ret.push_back(c);
68     }
69   }
70 
71   return ret;
72 }
73 
WriteStrings(ostream & out,const string & header,StringTable & strings,bool escape)74 static bool WriteStrings(ostream& out, const string& header,
75                          StringTable& strings, bool escape) {
76   out << "[" << header << "]" << std::endl;
77   for (const auto& iter : strings) {
78     out << iter.first << "=";
79     if (escape) {
80       out << Escape(iter.second);
81     } else {
82       out << iter.second;
83     }
84 
85     out << std::endl;
86   }
87 
88   return true;
89 }
90 
WriteStringsToFile(const string & path,const string & header,StringTable & strings,bool escape)91 static bool WriteStringsToFile(const string& path, const string& header,
92                                StringTable& strings, bool escape) {
93   ofstream* f = UIOpenWrite(path, ios::trunc);
94   bool success = false;
95   if (f->is_open()) {
96     success = WriteStrings(*f, header, strings, escape);
97     f->close();
98   }
99 
100   delete f;
101   return success;
102 }
103 
SaveSettings()104 void SaveSettings() {
105   /*
106    * NOTE! This code needs to stay in sync with the preference setting
107    *       code in in nsExceptionHandler.cpp.
108    */
109 
110   StringTable settings;
111 
112   ReadStringsFromFile(gSettingsPath + "/" + kIniFile, settings, true);
113   if (gIncludeURLCheck != 0)
114     settings["IncludeURL"] =
115         gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck)) ? "1"
116                                                                           : "0";
117   settings["SubmitReport"] =
118       gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck)) ? "1"
119                                                                           : "0";
120 
121   WriteStringsToFile(gSettingsPath + "/" + kIniFile, "Crash Reporter", settings,
122                      true);
123 }
124 
SendReport()125 void SendReport() {
126   LoadProxyinfo();
127 
128   // spawn a thread to do the sending
129   gSendThreadID = g_thread_create(SendThread, nullptr, TRUE, nullptr);
130 }
131 
DisableGUIAndSendReport()132 void DisableGUIAndSendReport() {
133   // disable all our gui controls, show the throbber + change the progress text
134   gtk_widget_set_sensitive(gSubmitReportCheck, FALSE);
135   gtk_widget_set_sensitive(gViewReportButton, FALSE);
136   gtk_widget_set_sensitive(gCommentText, FALSE);
137   if (gIncludeURLCheck) gtk_widget_set_sensitive(gIncludeURLCheck, FALSE);
138   gtk_widget_set_sensitive(gCloseButton, FALSE);
139   if (gRestartButton) gtk_widget_set_sensitive(gRestartButton, FALSE);
140   gtk_widget_show_all(gThrobber);
141   gtk_label_set_text(GTK_LABEL(gProgressLabel),
142                      gStrings[ST_REPORTDURINGSUBMIT].c_str());
143 
144   SendReport();
145 }
146 
ShowReportInfo(GtkTextView * viewReportTextView)147 static void ShowReportInfo(GtkTextView* viewReportTextView) {
148   GtkTextBuffer* buffer = gtk_text_view_get_buffer(viewReportTextView);
149 
150   GtkTextIter start, end;
151   gtk_text_buffer_get_start_iter(buffer, &start);
152   gtk_text_buffer_get_end_iter(buffer, &end);
153 
154   gtk_text_buffer_delete(buffer, &start, &end);
155 
156   for (Json::ValueConstIterator iter = gQueryParameters.begin();
157        iter != gQueryParameters.end(); ++iter) {
158     gtk_text_buffer_insert(buffer, &end, iter.name().c_str(),
159                            iter.name().length());
160     gtk_text_buffer_insert(buffer, &end, ": ", -1);
161     string value;
162     if (iter->isString()) {
163       value = iter->asString();
164     } else {
165       Json::StreamWriterBuilder builder;
166       builder["indentation"] = "";
167       value = writeString(builder, *iter);
168     }
169     gtk_text_buffer_insert(buffer, &end, value.c_str(), value.length());
170     gtk_text_buffer_insert(buffer, &end, "\n", -1);
171   }
172 
173   gtk_text_buffer_insert(buffer, &end, "\n", -1);
174   gtk_text_buffer_insert(buffer, &end, gStrings[ST_EXTRAREPORTINFO].c_str(),
175                          -1);
176 }
177 
UpdateSubmit()178 void UpdateSubmit() {
179   if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(gSubmitReportCheck))) {
180     gtk_widget_set_sensitive(gViewReportButton, TRUE);
181     gtk_widget_set_sensitive(gCommentText, TRUE);
182     if (gIncludeURLCheck) gtk_widget_set_sensitive(gIncludeURLCheck, TRUE);
183     gtk_label_set_text(GTK_LABEL(gProgressLabel),
184                        gStrings[ST_REPORTPRESUBMIT].c_str());
185   } else {
186     gtk_widget_set_sensitive(gViewReportButton, FALSE);
187     gtk_widget_set_sensitive(gCommentText, FALSE);
188     if (gIncludeURLCheck) gtk_widget_set_sensitive(gIncludeURLCheck, FALSE);
189     gtk_label_set_text(GTK_LABEL(gProgressLabel), "");
190   }
191 }
192 
ViewReportClicked(GtkButton * button,gpointer userData)193 static void ViewReportClicked(GtkButton* button, gpointer userData) {
194   GtkDialog* dialog = GTK_DIALOG(gtk_dialog_new_with_buttons(
195       gStrings[ST_VIEWREPORTTITLE].c_str(), GTK_WINDOW(gWindow),
196       GTK_DIALOG_MODAL, GTK_STOCK_OK, GTK_RESPONSE_OK, nullptr));
197 
198   GtkWidget* scrolled = gtk_scrolled_window_new(0, 0);
199   gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(dialog)),
200                     scrolled);
201   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
202                                  GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS);
203   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
204                                       GTK_SHADOW_IN);
205   gtk_widget_set_vexpand(scrolled, TRUE);
206 
207   GtkWidget* viewReportTextView = gtk_text_view_new();
208   gtk_container_add(GTK_CONTAINER(scrolled), viewReportTextView);
209   gtk_text_view_set_editable(GTK_TEXT_VIEW(viewReportTextView), FALSE);
210   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(viewReportTextView), GTK_WRAP_WORD);
211   gtk_widget_set_size_request(GTK_WIDGET(viewReportTextView), -1, 100);
212 
213   ShowReportInfo(GTK_TEXT_VIEW(viewReportTextView));
214 
215   gtk_dialog_set_default_response(dialog, GTK_RESPONSE_OK);
216   gtk_widget_set_size_request(GTK_WIDGET(dialog), 400, 200);
217   gtk_widget_show_all(GTK_WIDGET(dialog));
218   gtk_dialog_run(dialog);
219   gtk_widget_destroy(GTK_WIDGET(dialog));
220 }
221 
CommentChanged(GtkTextBuffer * buffer,gpointer userData)222 static void CommentChanged(GtkTextBuffer* buffer, gpointer userData) {
223   GtkTextIter start, end;
224   gtk_text_buffer_get_start_iter(buffer, &start);
225   gtk_text_buffer_get_end_iter(buffer, &end);
226   const char* comment = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
227   if (comment[0] == '\0' || gCommentFieldHint) {
228     gQueryParameters.removeMember("Comments");
229   } else {
230     gQueryParameters["Comments"] = comment;
231   }
232 }
233 
CommentInsert(GtkTextBuffer * buffer,GtkTextIter * location,gchar * text,gint len,gpointer userData)234 static void CommentInsert(GtkTextBuffer* buffer, GtkTextIter* location,
235                           gchar* text, gint len, gpointer userData) {
236   GtkTextIter start, end;
237   gtk_text_buffer_get_start_iter(buffer, &start);
238   gtk_text_buffer_get_end_iter(buffer, &end);
239   const char* comment = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
240 
241   // limit to 500 bytes in utf-8
242   if (strlen(comment) + len > MAX_COMMENT_LENGTH) {
243     g_signal_stop_emission_by_name(buffer, "insert-text");
244   }
245 }
246 
UpdateHintText(GtkWidget * widget,gboolean gainedFocus,bool * hintShowing,const char * hintText)247 static void UpdateHintText(GtkWidget* widget, gboolean gainedFocus,
248                            bool* hintShowing, const char* hintText) {
249   GtkTextBuffer* buffer = nullptr;
250   if (GTK_IS_TEXT_VIEW(widget))
251     buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
252 
253   if (gainedFocus) {
254     if (*hintShowing) {
255       if (buffer == nullptr) {  // sort of cheating
256         gtk_entry_set_text(GTK_ENTRY(widget), "");
257       } else {  // GtkTextView
258         gtk_text_buffer_set_text(buffer, "", 0);
259       }
260       gtk_widget_modify_text(widget, GTK_STATE_NORMAL, nullptr);
261       *hintShowing = false;
262     }
263   } else {
264     // lost focus
265     const char* text = nullptr;
266     if (buffer == nullptr) {
267       text = gtk_entry_get_text(GTK_ENTRY(widget));
268     } else {
269       GtkTextIter start, end;
270       gtk_text_buffer_get_start_iter(buffer, &start);
271       gtk_text_buffer_get_end_iter(buffer, &end);
272       text = gtk_text_buffer_get_text(buffer, &start, &end, TRUE);
273     }
274 
275     if (text == nullptr || text[0] == '\0') {
276       *hintShowing = true;
277 
278       if (buffer == nullptr) {
279         gtk_entry_set_text(GTK_ENTRY(widget), hintText);
280       } else {
281         gtk_text_buffer_set_text(buffer, hintText, -1);
282       }
283 
284       gtk_widget_modify_text(
285           widget, GTK_STATE_NORMAL,
286           &gtk_widget_get_style(widget)->text[GTK_STATE_INSENSITIVE]);
287     }
288   }
289 }
290 
CommentFocusChange(GtkWidget * widget,GdkEventFocus * event,gpointer userData)291 static gboolean CommentFocusChange(GtkWidget* widget, GdkEventFocus* event,
292                                    gpointer userData) {
293   UpdateHintText(widget, event->in, &gCommentFieldHint,
294                  gStrings[ST_COMMENTGRAYTEXT].c_str());
295 
296   return FALSE;
297 }
298 
299 typedef struct _GnomeProgram GnomeProgram;
300 typedef struct _GnomeModuleInfo GnomeModuleInfo;
301 typedef GnomeProgram* (*_gnome_program_init_fn)(const char*, const char*,
302                                                 const GnomeModuleInfo*, int,
303                                                 char**, const char*, ...);
304 typedef const GnomeModuleInfo* (*_libgnomeui_module_info_get_fn)();
305 
TryInitGnome()306 void TryInitGnome() {
307   gnomeLib = dlopen("libgnome-2.so.0", RTLD_LAZY);
308   if (!gnomeLib) return;
309 
310   gnomeuiLib = dlopen("libgnomeui-2.so.0", RTLD_LAZY);
311   if (!gnomeuiLib) return;
312 
313   _gnome_program_init_fn gnome_program_init =
314       (_gnome_program_init_fn)(dlsym(gnomeLib, "gnome_program_init"));
315   _libgnomeui_module_info_get_fn libgnomeui_module_info_get =
316       (_libgnomeui_module_info_get_fn)(dlsym(gnomeuiLib,
317                                              "libgnomeui_module_info_get"));
318 
319   if (gnome_program_init && libgnomeui_module_info_get) {
320     gnome_program_init("crashreporter", "1.0", libgnomeui_module_info_get(),
321                        gArgc, gArgv, nullptr);
322   }
323 }
324 
325 /* === Crashreporter UI Functions === */
326 
327 /*
328  * Anything not listed here is in crashreporter_gtk_common.cpp:
329  *  UIInit
330  *  UIShowDefaultUI
331  *  UIError_impl
332  *  UIGetIniPath
333  *  UIGetSettingsPath
334  *  UIEnsurePathExists
335  *  UIFileExists
336  *  UIMoveFile
337  *  UIDeleteFile
338  *  UIOpenRead
339  *  UIOpenWrite
340  */
341 
UIShutdown()342 void UIShutdown() {
343   if (gnomeuiLib) dlclose(gnomeuiLib);
344   // Don't dlclose gnomeLib as libgnomevfs and libORBit-2 use atexit().
345 }
346 
UIShowCrashUI(const StringTable & files,const Json::Value & queryParameters,const string & sendURL,const vector<string> & restartArgs)347 bool UIShowCrashUI(const StringTable& files, const Json::Value& queryParameters,
348                    const string& sendURL, const vector<string>& restartArgs) {
349   gFiles = files;
350   gQueryParameters = queryParameters;
351   gSendURL = sendURL;
352   gRestartArgs = restartArgs;
353   if (gQueryParameters.isMember("URL")) {
354     gURLParameter = gQueryParameters["URL"].asString();
355   }
356 
357   if (gAutoSubmit) {
358     SendReport();
359     CloseApp(nullptr);
360     return true;
361   }
362 
363   gWindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
364   gtk_window_set_title(GTK_WINDOW(gWindow),
365                        gStrings[ST_CRASHREPORTERTITLE].c_str());
366   gtk_window_set_resizable(GTK_WINDOW(gWindow), FALSE);
367   gtk_window_set_position(GTK_WINDOW(gWindow), GTK_WIN_POS_CENTER);
368   gtk_container_set_border_width(GTK_CONTAINER(gWindow), 12);
369   g_signal_connect(gWindow, "delete-event", G_CALLBACK(WindowDeleted), 0);
370   g_signal_connect(gWindow, "key_press_event", G_CALLBACK(check_escape),
371                    nullptr);
372 
373   GtkWidget* vbox = gtk_vbox_new(FALSE, 6);
374   gtk_container_add(GTK_CONTAINER(gWindow), vbox);
375 
376   GtkWidget* titleLabel = gtk_label_new("");
377   gtk_box_pack_start(GTK_BOX(vbox), titleLabel, FALSE, FALSE, 0);
378   gtk_misc_set_alignment(GTK_MISC(titleLabel), 0, 0.5);
379   char* markup =
380       g_strdup_printf("<b>%s</b>", gStrings[ST_CRASHREPORTERHEADER].c_str());
381   gtk_label_set_markup(GTK_LABEL(titleLabel), markup);
382   g_free(markup);
383 
384   GtkWidget* descriptionLabel =
385       gtk_label_new(gStrings[ST_CRASHREPORTERDESCRIPTION].c_str());
386   gtk_box_pack_start(GTK_BOX(vbox), descriptionLabel, TRUE, TRUE, 0);
387   // force the label to line wrap
388   gtk_label_set_max_width_chars(GTK_LABEL(descriptionLabel),
389                                 LABEL_MAX_CHAR_WIDTH);
390   gtk_label_set_line_wrap(GTK_LABEL(descriptionLabel), TRUE);
391   gtk_label_set_selectable(GTK_LABEL(descriptionLabel), TRUE);
392   gtk_misc_set_alignment(GTK_MISC(descriptionLabel), 0, 0.5);
393 
394   // this is honestly how they suggest you indent a section
395   GtkWidget* indentBox = gtk_hbox_new(FALSE, 0);
396   gtk_box_pack_start(GTK_BOX(vbox), indentBox, FALSE, FALSE, 0);
397   gtk_box_pack_start(GTK_BOX(indentBox), gtk_label_new(""), FALSE, FALSE, 6);
398 
399   GtkWidget* innerVBox1 = gtk_vbox_new(FALSE, 0);
400   gtk_box_pack_start(GTK_BOX(indentBox), innerVBox1, TRUE, TRUE, 0);
401 
402   gSubmitReportCheck =
403       gtk_check_button_new_with_label(gStrings[ST_CHECKSUBMIT].c_str());
404   gtk_box_pack_start(GTK_BOX(innerVBox1), gSubmitReportCheck, FALSE, FALSE, 0);
405   g_signal_connect(gSubmitReportCheck, "clicked",
406                    G_CALLBACK(SubmitReportChecked), 0);
407 
408   // indent again, below the "submit report" checkbox
409   GtkWidget* indentBox2 = gtk_hbox_new(FALSE, 0);
410   gtk_box_pack_start(GTK_BOX(innerVBox1), indentBox2, FALSE, FALSE, 0);
411   gtk_box_pack_start(GTK_BOX(indentBox2), gtk_label_new(""), FALSE, FALSE, 6);
412 
413   GtkWidget* innerVBox = gtk_vbox_new(FALSE, 0);
414   gtk_box_pack_start(GTK_BOX(indentBox2), innerVBox, TRUE, TRUE, 0);
415   gtk_box_set_spacing(GTK_BOX(innerVBox), 6);
416 
417   GtkWidget* viewReportButtonBox = gtk_hbutton_box_new();
418   gtk_box_pack_start(GTK_BOX(innerVBox), viewReportButtonBox, FALSE, FALSE, 0);
419   gtk_box_set_spacing(GTK_BOX(viewReportButtonBox), 6);
420   gtk_button_box_set_layout(GTK_BUTTON_BOX(viewReportButtonBox),
421                             GTK_BUTTONBOX_START);
422 
423   gViewReportButton =
424       gtk_button_new_with_label(gStrings[ST_VIEWREPORT].c_str());
425   gtk_box_pack_start(GTK_BOX(viewReportButtonBox), gViewReportButton, FALSE,
426                      FALSE, 0);
427   g_signal_connect(gViewReportButton, "clicked", G_CALLBACK(ViewReportClicked),
428                    0);
429 
430   GtkWidget* scrolled = gtk_scrolled_window_new(0, 0);
431   gtk_container_add(GTK_CONTAINER(innerVBox), scrolled);
432   gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
433                                  GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
434   gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
435                                       GTK_SHADOW_IN);
436   gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolled),
437                                              100);
438 
439   gCommentTextLabel = gtk_label_new(gStrings[ST_COMMENTGRAYTEXT].c_str());
440   gCommentText = gtk_text_view_new();
441   gtk_label_set_mnemonic_widget(GTK_LABEL(gCommentTextLabel), gCommentText);
442   gtk_text_view_set_accepts_tab(GTK_TEXT_VIEW(gCommentText), FALSE);
443   g_signal_connect(gCommentText, "focus-in-event",
444                    G_CALLBACK(CommentFocusChange), 0);
445   g_signal_connect(gCommentText, "focus-out-event",
446                    G_CALLBACK(CommentFocusChange), 0);
447 
448   GtkTextBuffer* commentBuffer =
449       gtk_text_view_get_buffer(GTK_TEXT_VIEW(gCommentText));
450   g_signal_connect(commentBuffer, "changed", G_CALLBACK(CommentChanged), 0);
451   g_signal_connect(commentBuffer, "insert-text", G_CALLBACK(CommentInsert), 0);
452 
453   gtk_container_add(GTK_CONTAINER(scrolled), gCommentText);
454   gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(gCommentText), GTK_WRAP_WORD_CHAR);
455   gtk_widget_set_size_request(GTK_WIDGET(gCommentText), -1, 100);
456 
457   if (gQueryParameters.isMember("URL")) {
458     gIncludeURLCheck =
459         gtk_check_button_new_with_label(gStrings[ST_CHECKURL].c_str());
460     gtk_box_pack_start(GTK_BOX(innerVBox), gIncludeURLCheck, FALSE, FALSE, 0);
461     g_signal_connect(gIncludeURLCheck, "clicked", G_CALLBACK(IncludeURLClicked),
462                      0);
463     // on by default
464     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(gIncludeURLCheck), TRUE);
465   }
466 
467   GtkWidget* progressBox = gtk_hbox_new(FALSE, 6);
468   gtk_box_pack_start(GTK_BOX(vbox), progressBox, TRUE, TRUE, 0);
469 
470   // Get the throbber image from alongside the executable
471   char* dir = g_path_get_dirname(gArgv[0]);
472   char* path = g_build_filename(dir, "Throbber-small.gif", nullptr);
473   g_free(dir);
474   gThrobber = gtk_image_new_from_file(path);
475   gtk_box_pack_start(GTK_BOX(progressBox), gThrobber, FALSE, FALSE, 0);
476 
477   gProgressLabel = gtk_label_new(gStrings[ST_REPORTPRESUBMIT].c_str());
478   gtk_box_pack_start(GTK_BOX(progressBox), gProgressLabel, TRUE, TRUE, 0);
479   // force the label to line wrap
480   gtk_label_set_max_width_chars(GTK_LABEL(gProgressLabel),
481                                 LABEL_MAX_CHAR_WIDTH);
482   gtk_label_set_line_wrap(GTK_LABEL(gProgressLabel), TRUE);
483 
484   GtkWidget* buttonBox = gtk_hbutton_box_new();
485   gtk_box_pack_end(GTK_BOX(vbox), buttonBox, FALSE, FALSE, 0);
486   gtk_box_set_spacing(GTK_BOX(buttonBox), 6);
487   gtk_button_box_set_layout(GTK_BUTTON_BOX(buttonBox), GTK_BUTTONBOX_END);
488 
489   gCloseButton = gtk_button_new_with_label(gStrings[ST_QUIT].c_str());
490   gtk_box_pack_start(GTK_BOX(buttonBox), gCloseButton, FALSE, FALSE, 0);
491   gtk_widget_set_can_default(gCloseButton, TRUE);
492   g_signal_connect(gCloseButton, "clicked", G_CALLBACK(CloseClicked), 0);
493 
494   gRestartButton = 0;
495   if (!restartArgs.empty()) {
496     gRestartButton = gtk_button_new_with_label(gStrings[ST_RESTART].c_str());
497     gtk_box_pack_start(GTK_BOX(buttonBox), gRestartButton, FALSE, FALSE, 0);
498     gtk_widget_set_can_default(gRestartButton, TRUE);
499     g_signal_connect(gRestartButton, "clicked", G_CALLBACK(RestartClicked), 0);
500   }
501 
502   gtk_widget_grab_focus(gSubmitReportCheck);
503 
504   gtk_widget_grab_default(gRestartButton ? gRestartButton : gCloseButton);
505 
506   LoadSettings();
507 
508   UpdateSubmit();
509 
510   UpdateHintText(gCommentText, FALSE, &gCommentFieldHint,
511                  gStrings[ST_COMMENTGRAYTEXT].c_str());
512 
513   gtk_widget_show_all(gWindow);
514   // stick this here to avoid the show_all above...
515   gtk_widget_hide(gThrobber);
516 
517   gtk_main();
518 
519   return gDidTrySend;
520 }
521