/
githubmirror
/
libreport
Обзор
Документация
Войти
/
githubmirror
/
libreport
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/gui-wizard-gtk/wizard.c
3 352 строки
118 KB
Matěj Grabovský
gtk: Fix segfault
01 июн 2021, 13:57
01 июн 2021, 13:57
a39a43c
Код
Авторство
О чём код?
/* Copyright (C) 2011 ABRT Team Copyright (C) 2011 RedHat inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include <gtk/gtk.h> #include <gdk/gdkkeysyms.h> #include "client.h" #include "internal_libreport_gtk.h" #include "wizard.h" #include "search_item.h" #include "libreport_types.h" #include "global_configuration.h" #define DEFAULT_WIDTH 800 #define DEFAULT_HEIGHT 500 #define FORBIDDEN_WORDS_BLACKLLIST "forbidden_words.conf" #define FORBIDDEN_WORDS_WHITELIST "ignored_words.conf" #define ELEMENT_IGNORE_LIST "ignored_elements.conf" typedef struct event_gui_data_t { char *event_name; GtkToggleButton *toggle_button; } event_gui_data_t; /* Using GHashTable as a set of file names */ /* Each table key has associated an nonzero integer and it allows us */ /* to write the following statements: */ /* if(g_hash_table_lookup(g_loaded_texts, FILENAME_COMMENT)) ... */ static GHashTable *g_loaded_texts; static char *g_event_selected; static pid_t g_event_child_pid = 0; static guint g_event_source_id = 0; static GtkCssProvider *g_provider = NULL; static GtkNotebook *g_assistant; static GtkWindow *g_wnd_assistant; static GtkBox *g_box_assistant; static GtkWidget *g_btn_stop; static GtkWidget *g_btn_close; static GtkWidget *g_btn_next; static GtkWidget *g_btn_repeat; static GtkWidget *g_btn_detail; static GtkBox *g_box_events; static GtkBox *g_box_workflows; static GtkLabel *g_lbl_event_log; static GtkTextView *g_tv_event_log; /* List of event_gui_data's */ /* List of event_gui_data's */ static GtkContainer *g_container_details1; static GtkContainer *g_container_details2; static GtkLabel *g_lbl_cd_reason; static GtkTextView *g_tv_comment; static GtkEventBox *g_eb_comment; static GtkCheckButton *g_cb_no_comment; static GtkBox *g_vb_simple_details; static GtkComboBoxText *g_cmb_reproducible; static GtkTextView *g_tv_steps; static GtkLabel *g_lbl_complex_details_hint; static GtkBox *g_vb_complex_details; static GtkWidget *g_widget_warnings_area; static GtkBox *g_box_warning_labels; static GtkToggleButton *g_tb_approve_bt; static GtkButton *g_btn_add_file; static GtkLabel *g_lbl_size; static GtkTreeView *g_tv_details; static GtkCellRenderer *g_tv_details_renderer_value; static GtkTreeViewColumn *g_tv_details_col_checkbox; //static GtkCellRenderer *g_tv_details_renderer_checkbox; static GtkListStore *g_ls_details; static GtkBox *g_box_buttons; //TODO: needs not be global static GtkNotebook *g_notebook; static GtkListStore *g_ls_sensitive_list; static GtkTreeView *g_tv_sensitive_list; static GtkTreeSelection *g_tv_sensitive_sel; static GtkRadioButton *g_rb_forbidden_words; static GtkRadioButton *g_rb_custom_search; static GtkExpander *g_exp_search; static gulong g_tv_sensitive_sel_hndlr; static gboolean g_warning_issued; static GtkWidget *g_report_stack; static GtkWidget *g_report_status_box; static GtkWidget *g_report_warning_box; static GtkSpinner *g_spinner_event_log; static GtkImage *g_img_process_fail; static GtkWidget *g_report_warning_label_box; static GtkExpander *g_exp_report_log; static GtkWidget *g_top_most_window; static void add_workflow_buttons(GtkBox *box, GHashTable *workflows, GCallback func); static void set_auto_event_chain(GtkButton *button, gpointer user_data); static void start_event_run(const char *event_name); static int tv_details_shown = 0; static GtkWidget *g_sens_ticket; static GtkToggleButton *g_sens_ticket_cb; enum { PRIV_WARN_SHOW_BTN = 0x01, PRIV_WARN_HIDE_BTN = 0x02, PRIV_WARN_SHOW_MSG = 0x04, PRIV_WARN_HIDE_MSG = 0x08, PRIV_WARN_BTN_CHECKED = 0x10, PRIV_WARN_BTN_UNCHECKED = 0x20, }; static void private_ticket_creation_warning(int flags); static void update_private_ticket_creation_warning_for_selected_event(void); enum { /* Note: need to update types in * gtk_list_store_new(DETAIL_NUM_COLUMNS, TYPE1, TYPE2...) * if you change these: */ DETAIL_COLUMN_CHECKBOX, DETAIL_COLUMN_NAME, DETAIL_COLUMN_VALUE, DETAIL_NUM_COLUMNS, }; /* Search in bt */ static guint g_timeout = 0; static GtkEntry *g_search_entry_bt; static const gchar *g_search_text; static search_item_t *g_current_highlighted_word; enum { SEARCH_COLUMN_FILE, SEARCH_COLUMN_TEXT, SEARCH_COLUMN_ITEM, }; static GtkBuilder *g_builder; /* THE PAGE FLOW * page_0: introduction/summary * page_1: user comments * page_2: event selection * page_3: backtrace editor * page_4: summary * page_5: reporting progress * page_6: finished */ enum { PAGENO_SUMMARY, // 0 PAGENO_EVENT_SELECTOR, // 1 PAGENO_EDIT_COMMENT, // 2 PAGENO_EDIT_ELEMENTS, // 3 PAGENO_REVIEW_DATA, // 4 PAGENO_EVENT_PROGRESS, // 5 PAGENO_EVENT_DONE, // 6 PAGENO_NOT_SHOWN, // 7 NUM_PAGES // 8 }; /* Use of arrays (instead of, say, #defines to C strings) * allows cheaper page_obj_t->name == PAGE_FOO comparisons * instead of strcmp. */ static const gchar PAGE_SUMMARY[] = "page_0"; static const gchar PAGE_EVENT_SELECTOR[] = "page_1"; static const gchar PAGE_EDIT_COMMENT[] = "page_2"; static const gchar PAGE_EDIT_ELEMENTS[] = "page_3"; static const gchar PAGE_REVIEW_DATA[] = "page_4"; static const gchar PAGE_EVENT_PROGRESS[] = "page_5"; static const gchar PAGE_EVENT_DONE[] = "page_6"; static const gchar PAGE_NOT_SHOWN[] = "page_7"; static const gchar *const page_names[] = { PAGE_SUMMARY, PAGE_EVENT_SELECTOR, PAGE_EDIT_COMMENT, PAGE_EDIT_ELEMENTS, PAGE_REVIEW_DATA, PAGE_EVENT_PROGRESS, PAGE_EVENT_DONE, PAGE_NOT_SHOWN, NULL }; #define SENSITIVE_DATA_WARN "sensitive_data_warning" #define SENSITIVE_DATA_WARN_MSG "sensitive_data_warning_message" #define SENSITIVE_LIST "ls_sensitive_words" static const gchar *misc_widgets[] = { SENSITIVE_DATA_WARN, SENSITIVE_LIST, NULL }; typedef struct { const gchar *name; const gchar *title; GtkWidget *page_widget; } page_obj_t; static page_obj_t pages[NUM_PAGES]; /* Utility functions */ static void clear_warnings(void); static void show_warnings(void); static void add_warning(const char *warning, GtkContainer *container); static bool check_minimal_bt_rating(const char *event_name, char **warning); static char *get_next_processed_event(GList **events_list); static void on_next_btn_cb(GtkWidget *btn, gpointer user_data); /* wizard.glade file as a string WIZARD_GLADE_CONTENTS: */ #include "wizard_glade.c" static GtkBuilder *make_builder() { GError *error = NULL; GtkBuilder *builder = gtk_builder_new(); /* load additional widgets from glade */ gtk_builder_add_objects_from_string(builder, WIZARD_GLADE_CONTENTS, sizeof(WIZARD_GLADE_CONTENTS) - 1, (gchar**)misc_widgets, &error); if (error != NULL) { error_msg_and_die("Error loading glade data: %s", error->message); } /* Load pages from internal string */ gtk_builder_add_objects_from_string(builder, WIZARD_GLADE_CONTENTS, sizeof(WIZARD_GLADE_CONTENTS) - 1, (gchar**)page_names, &error); if (error != NULL) { error_msg_and_die("Error loading glade data: %s", error->message); } return builder; } static void load_css_style() { g_provider = gtk_css_provider_new(); gtk_style_context_add_provider_for_screen(gdk_screen_get_default(), GTK_STYLE_PROVIDER(g_provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); const gchar *data = "#green_color {color: rgba(0%, 50%, 0%, 1);}\ #red_color {color: rgba(100%, 0%, 0%, 1);}\ #tev, #g_tv_event_log {font-family: monospace;}\ #g_eb_comment {color: #CC3333;}"; gtk_css_provider_load_from_data(g_provider, data, -1, NULL); g_object_unref (g_provider); } static void wrap_fixer(GtkWidget *widget, gpointer data_unused) { if (GTK_IS_CONTAINER(widget)) { gtk_container_foreach((GtkContainer*)widget, wrap_fixer, NULL); return; } if (GTK_IS_LABEL(widget)) { GtkLabel *label = (GtkLabel*)widget; if (gtk_label_get_line_wrap(label) && (gtk_widget_get_halign(widget) == GTK_ALIGN_START) && (gtk_widget_get_margin_top(widget) == 0) && (gtk_widget_get_margin_bottom(widget) == 0) ) { libreport_make_label_autowrap_on_resize(label); return; } } } static void fix_all_wrapped_labels(GtkWidget *widget) { wrap_fixer(widget, NULL); } static void remove_child_widget(GtkWidget *widget, gpointer unused) { /* Destroy will safely remove it and free the memory * if there are no refs left */ gtk_widget_destroy(widget); } static void update_window_title(void) { /* prgname can be null according to gtk documentation */ const char *prgname = g_get_prgname(); const char *reason = problem_data_get_content_or_NULL(g_cd, FILENAME_REASON); g_autofree char *title = g_strdup_printf("%s - %s", (reason ? reason : g_dump_dir_name), (prgname ? prgname : "report")); gtk_window_set_title(g_wnd_assistant, title); } static bool ask_continue_before_steal(const char *base_dir, const char *dump_dir) { g_autofree char *msg = g_strdup_printf( _("Need writable directory, but '%s' is not writable." " Move it to '%s' and operate on the moved data?"), dump_dir, base_dir); const bool response = libreport_run_ask_yes_no_yesforever_dialog("ask_steal_dir", msg, GTK_WINDOW(g_wnd_assistant)); return response; } struct dump_dir *wizard_open_directory_for_writing(const char *dump_dir_name) { struct dump_dir *dd = libreport_open_directory_for_writing(dump_dir_name, ask_continue_before_steal); if (dd && strcmp(g_dump_dir_name, dd->dd_dirname) != 0) { free(g_dump_dir_name); g_dump_dir_name = g_strdup(dd->dd_dirname); update_window_title(); } return dd; } void show_error_as_msgbox(const char *msg) { g_return_if_fail(g_wnd_assistant != NULL); GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_wnd_assistant), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, "%s", msg ); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } static void load_text_to_text_view(GtkTextView *tv, const char *name) { /* Add to set of loaded files */ /* a key_destroy_func() is provided therefore if the key for name already exists */ /* a result of g_strdup() is freed */ g_hash_table_insert(g_loaded_texts, (gpointer)g_strdup(name), (gpointer)1); const char *str = g_cd ? problem_data_get_content_or_NULL(g_cd, name) : NULL; /* Bad: will choke at any text with non-Unicode parts: */ /* gtk_text_buffer_set_text(tb, (str ? str : ""), -1);*/ /* Start torturing ourself instead: */ libreport_reload_text_to_text_view(tv, str); } static gchar *get_malloced_string_from_text_view(GtkTextView *tv) { GtkTextBuffer *buffer = gtk_text_view_get_buffer(tv); GtkTextIter start; GtkTextIter end; gtk_text_buffer_get_start_iter(buffer, &start); gtk_text_buffer_get_end_iter(buffer, &end); return gtk_text_buffer_get_text(buffer, &start, &end, FALSE); } static void save_text_if_changed(const char *name, const char *new_value) { /* a text value can't be change if the file is not loaded */ /* returns NULL if the name is not found; otherwise nonzero */ if (!g_hash_table_lookup(g_loaded_texts, name)) return; const char *old_value = NULL; if (g_cd) old_value = problem_data_get_content_or_NULL(g_cd, name); if (!old_value) old_value = ""; if (strcmp(new_value, old_value) != 0) { /* If the dump directory cannot be opened for writing, an error dialogue * will pop up because libreport_g_custom_logger is set to &show_error_as_msgbox.*/ struct dump_dir *dd = wizard_open_directory_for_writing(g_dump_dir_name); if (dd) /* If this operation fails, an error dialogue will pop up because * libreport_g_custom_logger is set to &show_error_as_msgbox.*/ dd_save_text(dd, name, new_value); dd_close(dd); } } static void save_text_from_text_view(GtkTextView *tv, const char *name) { g_autofree char *new_str = get_malloced_string_from_text_view(tv); save_text_if_changed(name, new_str); } static void append_to_textview(GtkTextView *tv, const char *str) { GtkTextBuffer *tb = gtk_text_view_get_buffer(tv); /* Ensure we insert text at the end */ GtkTextIter text_iter; gtk_text_buffer_get_end_iter(tb, &text_iter); gtk_text_buffer_place_cursor(tb, &text_iter); /* Deal with possible broken Unicode */ const gchar *end; while (!g_utf8_validate(str, -1, &end)) { gtk_text_buffer_insert_at_cursor(tb, str, end - str); char buf[8]; unsigned len = snprintf(buf, sizeof(buf), "<%02X>", (unsigned char)*end); gtk_text_buffer_insert_at_cursor(tb, buf, len); str = end + 1; } gtk_text_buffer_get_end_iter(tb, &text_iter); const char *last = str; GList *urls = libreport_find_url_tokens(str); for (GList *u = urls; u; u = g_list_next(u)) { const struct libreport_url_token *const t = (struct libreport_url_token *)u->data; if (last < t->start) gtk_text_buffer_insert(tb, &text_iter, last, t->start - last); GtkTextTag *tag; tag = gtk_text_buffer_create_tag(tb, NULL, "foreground", "blue", "underline", PANGO_UNDERLINE_SINGLE, NULL); char *url = g_strndup(t->start, t->len); g_object_set_data_full(G_OBJECT(tag), "url", url, g_free); gtk_text_buffer_insert_with_tags(tb, &text_iter, url, -1, tag, NULL); last = t->start + t->len; } g_list_free_full(urls, g_free); if (last[0] != '\0') gtk_text_buffer_insert(tb, &text_iter, last, strlen(last)); /* Scroll so that the end of the log is visible */ gtk_text_view_scroll_to_iter(tv, &text_iter, /*within_margin:*/ 0.0, /*use_align:*/ FALSE, /*xalign:*/ 0, /*yalign:*/ 0); } /* Looks at all tags covering the position of iter in the text view, * and if one of them is a link, follow it by showing the page identified * by the data attached to it. */ static void open_browse_if_link(GtkWidget *text_view, GtkTextIter *iter) { GSList *tags = NULL, *tagp = NULL; tags = gtk_text_iter_get_tags (iter); for (tagp = tags; tagp != NULL; tagp = tagp->next) { GtkTextTag *tag = tagp->data; const char *url = g_object_get_data (G_OBJECT (tag), "url"); if (url != NULL) { /* http://techbase.kde.org/KDE_System_Administration/Environment_Variables#KDE_FULL_SESSION */ if (getenv("KDE_FULL_SESSION") != NULL) { gint exitcode; gchar *arg[3]; /* kde-open is from kdebase-runtime, it should be there. */ arg[0] = (char *) "kde-open"; arg[1] = (char *) url; arg[2] = NULL; const gboolean spawn_ret = g_spawn_sync(NULL, arg, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, NULL, &exitcode, NULL); if (spawn_ret) break; } GError *error = NULL; #if ((GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 22) || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION == 22 && GTK_MICRO_VERSION < 5)) if (!gtk_show_uri(/* use default screen */ NULL, url, GDK_CURRENT_TIME, &error)) #else if (!gtk_show_uri_on_window(NULL, url, GDK_CURRENT_TIME, &error)) #endif error_msg("Can't open url '%s': %s", url, error->message); break; } } if (tags) g_slist_free (tags); } /* Links can be activated by pressing Enter. */ static gboolean key_press_event(GtkWidget *text_view, GdkEventKey *event) { GtkTextIter iter; GtkTextBuffer *buffer; switch (event->keyval) { case GDK_KEY_Return: case GDK_KEY_KP_Enter: buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); gtk_text_buffer_get_iter_at_mark(buffer, &iter, gtk_text_buffer_get_insert(buffer)); open_browse_if_link(text_view, &iter); break; default: break; } return FALSE; } /* Links can also be activated by clicking. */ static gboolean event_after(GtkWidget *text_view, GdkEvent *ev) { GtkTextIter start, end, iter; GtkTextBuffer *buffer; GdkEventButton *event; gint x, y; if (ev->type != GDK_BUTTON_RELEASE) return FALSE; event = (GdkEventButton *)ev; if (event->button != GDK_BUTTON_PRIMARY) return FALSE; buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(text_view)); /* we shouldn't follow a link if the user has selected something */ gtk_text_buffer_get_selection_bounds(buffer, &start, &end); if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end)) return FALSE; gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW (text_view), GTK_TEXT_WINDOW_WIDGET, event->x, event->y, &x, &y); gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW (text_view), &iter, x, y); open_browse_if_link(text_view, &iter); return FALSE; } static gboolean hovering_over_link = FALSE; static GdkCursor *hand_cursor = NULL; static GdkCursor *regular_cursor = NULL; /* Looks at all tags covering the position (x, y) in the text view, * and if one of them is a link, change the cursor to the "hands" cursor * typically used by web browsers. */ static void set_cursor_if_appropriate(GtkTextView *text_view, gint x, gint y) { GSList *tags = NULL, *tagp = NULL; GtkTextIter iter; gboolean hovering = FALSE; gtk_text_view_get_iter_at_location(text_view, &iter, x, y); tags = gtk_text_iter_get_tags(&iter); for (tagp = tags; tagp != NULL; tagp = tagp->next) { GtkTextTag *tag = tagp->data; gpointer url = g_object_get_data(G_OBJECT (tag), "url"); if (url != NULL) { hovering = TRUE; break; } } if (hovering != hovering_over_link) { hovering_over_link = hovering; if (hovering_over_link) gdk_window_set_cursor(gtk_text_view_get_window(text_view, GTK_TEXT_WINDOW_TEXT), hand_cursor); else gdk_window_set_cursor(gtk_text_view_get_window(text_view, GTK_TEXT_WINDOW_TEXT), regular_cursor); } if (tags) g_slist_free (tags); } /* Update the cursor image if the pointer moved. */ static gboolean motion_notify_event(GtkWidget *text_view, GdkEventMotion *event) { gint x, y; gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text_view), GTK_TEXT_WINDOW_WIDGET, event->x, event->y, &x, &y); set_cursor_if_appropriate(GTK_TEXT_VIEW(text_view), x, y); return FALSE; } /* Also update the cursor image if the window becomes visible * (e.g. when a window covering it got iconified). */ static gboolean visibility_notify_event(GtkWidget *text_view, GdkEventVisibility *event) { gint wx, wy, bx, by; GdkWindow *win = gtk_text_view_get_window(GTK_TEXT_VIEW(text_view), GTK_TEXT_WINDOW_TEXT); GdkSeat *display_seat = gdk_display_get_default_seat(gdk_window_get_display(win)); GdkDevice *pointer = gdk_seat_get_pointer(display_seat); gdk_window_get_device_position(gtk_widget_get_window(text_view), pointer, &wx, &wy, NULL); gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(text_view), GTK_TEXT_WINDOW_WIDGET, wx, wy, &bx, &by); set_cursor_if_appropriate(GTK_TEXT_VIEW (text_view), bx, by); return FALSE; } /* tv_details handling */ static struct problem_item *get_current_problem_item_or_NULL(GtkTreeView *tree_view, gchar **pp_item_name) { GtkTreeModel *model; GtkTreeIter iter; GtkTreeSelection* selection = gtk_tree_view_get_selection(tree_view); if (selection == NULL) return NULL; if (!gtk_tree_selection_get_selected(selection, &model, &iter)) return NULL; *pp_item_name = NULL; gtk_tree_model_get(model, &iter, DETAIL_COLUMN_NAME, pp_item_name, -1); if (!*pp_item_name) /* paranoia, should never happen */ return NULL; struct problem_item *item = problem_data_get_item_or_NULL(g_cd, *pp_item_name); return item; } static void tv_details_row_activated( GtkTreeView *tree_view, GtkTreePath *tree_path_UNUSED, GtkTreeViewColumn *column, gpointer user_data) { g_autofree gchar *item_name = NULL; struct problem_item *item = get_current_problem_item_or_NULL(tree_view, &item_name); if (!item || !(item->flags & CD_FLAG_TXT)) return; if (!strchr(item->content, '\n')) /* one line? */ return; gint exitcode; gchar *arg[3]; arg[0] = (char *) "xdg-open"; arg[1] = g_build_filename(g_dump_dir_name ? g_dump_dir_name : "", item_name, NULL); arg[2] = NULL; const gboolean spawn_ret = g_spawn_sync(NULL, arg, NULL, G_SPAWN_SEARCH_PATH | G_SPAWN_STDOUT_TO_DEV_NULL, NULL, NULL, NULL, NULL, &exitcode, NULL); if (spawn_ret == FALSE || exitcode != EXIT_SUCCESS) { GtkWidget *dialog = gtk_dialog_new_with_buttons(_("View/edit a text file"), GTK_WINDOW(g_wnd_assistant), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, NULL, NULL); GtkWidget *vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); GtkWidget *scrolled = gtk_scrolled_window_new(NULL, NULL); GtkWidget *textview = gtk_text_view_new(); gtk_dialog_add_button(GTK_DIALOG(dialog), _("_Save"), GTK_RESPONSE_OK); gtk_dialog_add_button(GTK_DIALOG(dialog), _("_Cancel"), GTK_RESPONSE_CANCEL); gtk_box_pack_start(GTK_BOX(vbox), scrolled, TRUE, TRUE, 0); gtk_widget_set_size_request(scrolled, 640, 480); gtk_widget_show(scrolled); /* gtk_container_add() will now automatically add a GtkViewport if the child doesn't implement GtkScrollable. */ gtk_container_add(GTK_CONTAINER(scrolled), textview); gtk_widget_show(textview); load_text_to_text_view(GTK_TEXT_VIEW(textview), item_name); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { save_text_from_text_view(GTK_TEXT_VIEW(textview), item_name); problem_data_reload_from_dump_dir(); update_gui_state_from_problem_data(/* don't update selected event */ 0); } gtk_widget_destroy(textview); gtk_widget_destroy(scrolled); gtk_widget_destroy(dialog); } g_free(arg[1]); } /* static gboolean tv_details_select_cursor_row( GtkTreeView *tree_view, gboolean arg1, gpointer user_data) {...} */ static void tv_details_show( GtkWidget *widget_UNUSED, gpointer user_data_UNUSED) { tv_details_shown = 1; } static void tv_details_cursor_changed( GtkTreeView *tree_view, gpointer user_data_UNUSED) { /* I see this being called on window "destroy" signal when the tree_view is not a tree view anymore (or destroyed?) causing this error msg: (abrt:12804): Gtk-CRITICAL **: gtk_tree_selection_get_selected: assertion `GTK_IS_TREE_SELECTION (selection)' failed (abrt:12804): GLib-GObject-WARNING **: invalid uninstantiatable type `(null)' in cast to `GObject' (abrt:12804): GLib-GObject-CRITICAL **: g_object_set: assertion `G_IS_OBJECT (object)' failed */ if (!GTK_IS_TREE_VIEW(tree_view)) return; g_autofree gchar *item_name = NULL; struct problem_item *item = get_current_problem_item_or_NULL(tree_view, &item_name); /* happens when closing the wizard by clicking 'X' */ if (!item) return; gboolean editable = (item /* With this, copying of non-editable fields are more difficult */ //&& (item->flags & CD_FLAG_ISEDITABLE) && (item->flags & CD_FLAG_TXT) && !strchr(item->content, '\n') ); /* Allow user to select the text with mouse. * Has undesirable side-effect of allowing user to "edit" the text, * but changes aren't saved (the old text reappears as soon as user * leaves the field). Need to disable editing somehow. */ g_object_set(G_OBJECT(g_tv_details_renderer_value), "editable", editable, NULL); } static void g_tv_details_checkbox_toggled( GtkCellRendererToggle *cell_renderer_UNUSED, gchar *tree_path, gpointer user_data_UNUSED) { GtkTreeIter iter; if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(g_ls_details), &iter, tree_path)) return; g_autofree gchar *item_name = NULL; gtk_tree_model_get(GTK_TREE_MODEL(g_ls_details), &iter, DETAIL_COLUMN_NAME, &item_name, -1); if (!item_name) /* paranoia, should never happen */ return; struct problem_item *item = problem_data_get_item_or_NULL(g_cd, item_name); if (!item) /* paranoia */ return; int cur_value; if (item->selected_by_user == 0) cur_value = item->default_by_reporter; else cur_value = !!(item->selected_by_user + 1); /* map -1,1 to 0,1 */ if (item->allowed_by_reporter && !item->required_by_reporter) { cur_value = !cur_value; item->selected_by_user = cur_value * 2 - 1; /* map 0,1 to -1,1 */ gtk_list_store_set(g_ls_details, &iter, DETAIL_COLUMN_CHECKBOX, cur_value, -1); } } static void check_event_config(const char *event_name) { GList *errors = get_options_with_err_msg(event_name); if (errors != NULL) { g_list_free_full(errors, (GDestroyNotify)free_invalid_options); libreport_show_event_config_dialog(event_name, GTK_WINDOW(g_top_most_window)); update_private_ticket_creation_warning_for_selected_event(); } } static bool isdigit_str(const char *str) { do { if (*str < '0' || *str > '9') return false; str++; } while (*str); return true; } static void update_reproducible_hints(void) { int reproducible = gtk_combo_box_get_active(GTK_COMBO_BOX(g_cmb_reproducible)); switch(reproducible) { case -1: return; case PROBLEM_REPRODUCIBLE_UNKNOWN: gtk_label_set_text(g_lbl_complex_details_hint, _("Since crashes without a known reproducer can be " "difficult to diagnose, please provide a comprehensive " "description of the problem you have encountered.")); break; case PROBLEM_REPRODUCIBLE_YES: gtk_label_set_text(g_lbl_complex_details_hint, _("Please provide a short description of the problem and " "please include the steps you have used to reproduce " "the problem.")); break; case PROBLEM_REPRODUCIBLE_RECURRENT: gtk_label_set_text(g_lbl_complex_details_hint, _("Please provide the steps you have used to reproduce the " "problem.")); break; default: error_msg("BUG: %s:%s:%d: forgotten 'how reproducible' value", __FILE__, __func__, __LINE__); break; } } struct cd_stats { off_t filesize; unsigned filecount; }; static void save_items_from_notepad(void) { gint n_pages = gtk_notebook_get_n_pages(g_notebook); int i = 0; GtkWidget *notebook_child; GtkTextView *tev; GtkWidget *tab_lbl; const char *item_name; for (i = 0; i < n_pages; i++) { notebook_child = gtk_notebook_get_nth_page(g_notebook, i); tev = GTK_TEXT_VIEW(gtk_bin_get_child(GTK_BIN(notebook_child))); tab_lbl = gtk_notebook_get_tab_label(g_notebook, notebook_child); item_name = gtk_label_get_text(GTK_LABEL(tab_lbl)); log_info("Saving item '%s'", item_name); save_text_from_text_view(tev, item_name); } } static void remove_tabs_from_notebook(GtkNotebook *notebook) { gint n_pages = gtk_notebook_get_n_pages(notebook); int ii; for (ii = 0; ii < n_pages; ii++) { /* removing a page changes the indices, so we always need to remove * page 0 */ gtk_notebook_remove_page(notebook, 0); //we need to always the page 0 } /* Turn off the changed callback during the update */ g_signal_handler_block(g_tv_sensitive_sel, g_tv_sensitive_sel_hndlr); g_current_highlighted_word = NULL; GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_ls_sensitive_list), &iter); while (valid) { g_autofree char *text = NULL; g_autofree search_item_t *word = NULL; gtk_tree_model_get(GTK_TREE_MODEL(g_ls_sensitive_list), &iter, SEARCH_COLUMN_TEXT, &text, SEARCH_COLUMN_ITEM, &word, -1); valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(g_ls_sensitive_list), &iter); } gtk_list_store_clear(g_ls_sensitive_list); g_signal_handler_unblock(g_tv_sensitive_sel, g_tv_sensitive_sel_hndlr); } static void append_item_to_ls_details(gpointer name, gpointer value, gpointer data) { if (g_provider == NULL) load_css_style(); problem_item *item = (problem_item*)value; struct cd_stats *stats = data; GtkTreeIter iter; gtk_list_store_append(g_ls_details, &iter); stats->filecount++; //FIXME: use the human-readable problem_item_format(item) instead of item->content. if (item->flags & CD_FLAG_TXT) { if (item->flags & CD_FLAG_ISEDITABLE && strcmp(name, FILENAME_ANACONDA_TB) != 0) { GtkWidget *tab_lbl = gtk_label_new((char *)name); GtkWidget *tev = gtk_text_view_new(); gtk_widget_set_name(GTK_WIDGET(tev), "tev"); if (strcmp(name, FILENAME_COMMENT) == 0 || strcmp(name, FILENAME_REASON) == 0) gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(tev), GTK_WRAP_WORD); load_text_to_text_view(GTK_TEXT_VIEW(tev), (char *)name); /* init searching */ GtkTextBuffer *buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(tev)); /* found items background */ gtk_text_buffer_create_tag(buf, "search_result_bg", "background", "red", NULL); gtk_text_buffer_create_tag(buf, "current_result_bg", "background", "green", NULL); GtkWidget *sw = gtk_scrolled_window_new(NULL, NULL); gtk_container_add(GTK_CONTAINER(sw), tev); gtk_notebook_append_page(g_notebook, sw, tab_lbl); } stats->filesize += strlen(item->content); /* If not multiline... */ if (!strchr(item->content, '\n')) { gtk_list_store_set(g_ls_details, &iter, DETAIL_COLUMN_NAME, (char *)name, DETAIL_COLUMN_VALUE, item->content, -1); } else { gtk_list_store_set(g_ls_details, &iter, DETAIL_COLUMN_NAME, (char *)name, DETAIL_COLUMN_VALUE, _("(click here to view/edit)"), -1); } } else if (item->flags & CD_FLAG_BIN) { struct stat statbuf; statbuf.st_size = 0; if (stat(item->content, &statbuf) == 0) { stats->filesize += statbuf.st_size; g_autofree char *msg = g_strdup_printf( _("(binary file, %llu bytes)"), (long long)statbuf.st_size); gtk_list_store_set(g_ls_details, &iter, DETAIL_COLUMN_NAME, (char *)name, DETAIL_COLUMN_VALUE, msg, -1); } } int cur_value; if (item->selected_by_user == 0) cur_value = item->default_by_reporter; else cur_value = !!(item->selected_by_user + 1); /* map -1,1 to 0,1 */ gtk_list_store_set(g_ls_details, &iter, DETAIL_COLUMN_CHECKBOX, cur_value, -1); } /* Based on selected reporter, update item checkboxes */ static void update_ls_details_checkboxes(const char *event_name) { event_config_t *cfg = get_event_config(event_name); GHashTableIter iter; char *name; struct problem_item *item; g_hash_table_iter_init(&iter, g_cd); string_vector_ptr_t global_exclude = libreport_get_global_always_excluded_elements(); while (g_hash_table_iter_next(&iter, (void**)&name, (void**)&item)) { /* Decide whether item is allowed, required, and what's the default */ item->allowed_by_reporter = 1; if (global_exclude) item->allowed_by_reporter = !libreport_is_in_string_list(name, (const_string_vector_const_ptr_t)global_exclude); if (cfg) { if (libreport_is_in_comma_separated_list_of_glob_patterns(name, cfg->ec_exclude_items_always)) item->allowed_by_reporter = 0; if ((item->flags & CD_FLAG_BIN) && cfg->ec_exclude_binary_items) item->allowed_by_reporter = 0; } item->default_by_reporter = item->allowed_by_reporter; if (cfg) { if (libreport_is_in_comma_separated_list_of_glob_patterns(name, cfg->ec_exclude_items_by_default)) item->default_by_reporter = 0; if (libreport_is_in_comma_separated_list_of_glob_patterns(name, cfg->ec_include_items_by_default)) item->allowed_by_reporter = item->default_by_reporter = 1; } item->required_by_reporter = 0; if (cfg) { if (libreport_is_in_comma_separated_list_of_glob_patterns(name, cfg->ec_requires_items)) item->default_by_reporter = item->allowed_by_reporter = item->required_by_reporter = 1; } int cur_value; if (item->selected_by_user == 0) cur_value = item->default_by_reporter; else cur_value = !!(item->selected_by_user + 1); /* map -1,1 to 0,1 */ /* Find corresponding line and update checkbox */ GtkTreeIter iter; if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_ls_details), &iter)) { do { g_autofree gchar *item_name = NULL; gtk_tree_model_get(GTK_TREE_MODEL(g_ls_details), &iter, DETAIL_COLUMN_NAME, &item_name, -1); if (!item_name) /* paranoia, should never happen */ continue; int differ = strcmp(name, item_name); if (differ) continue; gtk_list_store_set(g_ls_details, &iter, DETAIL_COLUMN_CHECKBOX, cur_value, -1); break; } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(g_ls_details), &iter)); } } /* Free allocated resources before exiting */ g_strfreev(global_exclude); } void update_gui_state_from_problem_data(int flags) { update_window_title(); remove_tabs_from_notebook(g_notebook); const char *reason = problem_data_get_content_or_NULL(g_cd, FILENAME_REASON); const char *not_reportable = problem_data_get_content_or_NULL(g_cd, FILENAME_NOT_REPORTABLE); g_autofree char *t = g_strdup_printf("%s%s%s", not_reportable ? : "", not_reportable ? " " : "", reason ? : _("(no description)")); gtk_label_set_text(g_lbl_cd_reason, t); gtk_list_store_clear(g_ls_details); struct cd_stats stats = { 0 }; g_hash_table_foreach(g_cd, append_item_to_ls_details, &stats); g_autofree char *msg = g_strdup_printf( _("%llu bytes, %u files"), (long long)stats.filesize, stats.filecount); gtk_label_set_text(g_lbl_size, msg); load_text_to_text_view(g_tv_comment, FILENAME_COMMENT); load_text_to_text_view(g_tv_steps, FILENAME_REPRODUCER); add_workflow_buttons(g_box_workflows, g_workflow_list, G_CALLBACK(set_auto_event_chain)); /* We can't just do gtk_widget_show_all once in main: * We created new widgets (buttons). Need to make them visible. */ gtk_widget_show_all(GTK_WIDGET(g_wnd_assistant)); /* Update Reproducible */ /* Try to get the old value */ const int reproducible = get_problem_data_reproducible(g_cd); if (reproducible > -1) { gtk_combo_box_set_active(GTK_COMBO_BOX(g_cmb_reproducible), reproducible); goto reproducible_done; } /* OK, no old value. * Try to guess the reproducibility from the number of occurrences */ const char *count_str = problem_data_get_content_or_NULL(g_cd, FILENAME_COUNT); if ( count_str == NULL || strcmp(count_str, "0") == 0 || strcmp(count_str, "1") == 0 || strcmp(count_str, "2") == 0 || !isdigit_str(count_str)) { gtk_combo_box_set_active(GTK_COMBO_BOX(g_cmb_reproducible), PROBLEM_REPRODUCIBLE_UNKNOWN); } else { char *endptr; int count = INT_MIN; long cnt = g_ascii_strtoll(count_str, &endptr, 10); if (cnt >= INT_MIN && cnt <= INT_MAX && count_str != endptr) count = (int)cnt; else error_msg_and_die("expected number in range <%d, %d>: '%s'", INT_MIN, INT_MAX, count_str); if (count < 5) gtk_combo_box_set_active(GTK_COMBO_BOX(g_cmb_reproducible), PROBLEM_REPRODUCIBLE_YES); else gtk_combo_box_set_active(GTK_COMBO_BOX(g_cmb_reproducible), PROBLEM_REPRODUCIBLE_RECURRENT); } reproducible_done: update_reproducible_hints(); } /* start_event_run */ struct analyze_event_data { struct run_event_state *run_state; char *event_name; GList *env_list; GIOChannel *channel; GString *event_log; int event_log_state; int fd; /*guint event_source_id;*/ }; enum { LOGSTATE_FIRSTLINE = 0, LOGSTATE_BEGLINE, LOGSTATE_ERRLINE, LOGSTATE_MIDLINE, }; static void set_excluded_envvar(void) { GString *item_list = g_string_new(NULL); const char *fmt = "%s"; GtkTreeIter iter; if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_ls_details), &iter)) { do { g_autofree gchar *item_name = NULL; gboolean checked = 0; gtk_tree_model_get(GTK_TREE_MODEL(g_ls_details), &iter, DETAIL_COLUMN_NAME, &item_name, DETAIL_COLUMN_CHECKBOX, &checked, -1); if (!item_name) /* paranoia, should never happen */ continue; if (!checked) { g_string_append_printf(item_list, fmt, item_name); fmt = ", %s"; } } while (gtk_tree_model_iter_next(GTK_TREE_MODEL(g_ls_details), &iter)); } g_autofree char *var = g_string_free(item_list, FALSE); if (var) { gboolean ret = g_setenv("EXCLUDE_FROM_REPORT", var, TRUE); if (!ret) { error_msg_and_die(_("Unable to set EXCLUDE_FROM_REPORT environment variable.")); } } else { unsetenv("EXCLUDE_FROM_REPORT"); } } static int spawn_next_command_in_evd(struct analyze_event_data *evd) { evd->env_list = export_event_config(evd->event_name); int r = spawn_next_command(evd->run_state, g_dump_dir_name, evd->event_name, EXECFLG_SETPGID); if (r >= 0) { g_event_child_pid = evd->run_state->command_pid; } else { unexport_event_config(evd->env_list); evd->env_list = NULL; } return r; } static void save_to_event_log(struct analyze_event_data *evd, const char *str) { static const char delim[] = { [LOGSTATE_FIRSTLINE] = '>', [LOGSTATE_BEGLINE] = ' ', [LOGSTATE_ERRLINE] = '*', }; while (str[0]) { char *end = strchrnul(str, '\n'); char end_char = *end; if (end_char == '\n') end++; switch (evd->event_log_state) { case LOGSTATE_FIRSTLINE: case LOGSTATE_BEGLINE: case LOGSTATE_ERRLINE: /* skip empty lines */ if (str[0] == '\n') goto next; g_string_append_printf(evd->event_log, "%s%c %.*s", libreport_iso_date_string(NULL), delim[evd->event_log_state], (int)(end - str), str ); break; case LOGSTATE_MIDLINE: g_string_append_printf(evd->event_log, "%.*s", (int)(end - str), str); break; } evd->event_log_state = LOGSTATE_MIDLINE; if (end_char != '\n') break; evd->event_log_state = LOGSTATE_BEGLINE; next: str = end; } } static void update_event_log_on_disk(const char *str) { /* Load existing log */ struct dump_dir *dd = dd_opendir(g_dump_dir_name, 0); if (!dd) return; g_autofree char *event_log = dd_load_text_ext(dd, FILENAME_EVENT_LOG, DD_FAIL_QUIETLY_ENOENT); /* Append new log part to existing log */ unsigned len = strlen(event_log); if (len != 0 && event_log[len - 1] != '\n') event_log = libreport_append_to_malloced_string(event_log, "\n"); event_log = libreport_append_to_malloced_string(event_log, str); /* Trim log according to size watermarks */ len = strlen(event_log); char *new_log = event_log; if (len > EVENT_LOG_HIGH_WATERMARK) { new_log += len - EVENT_LOG_LOW_WATERMARK; new_log = strchrnul(new_log, '\n'); if (new_log[0]) new_log++; } /* Save */ dd_save_text(dd, FILENAME_EVENT_LOG, new_log); dd_close(dd); } static bool cancel_event_run() { if (g_event_child_pid <= 0) return false; kill(- g_event_child_pid, SIGTERM); return true; } static void on_btn_cancel_event(GtkButton *button) { cancel_event_run(); } static bool is_processing_finished() { return NULL == g_auto_event_list; } static void hide_next_step_button() { /* replace 'Forward' with 'Close' button */ /* 1. hide next button */ gtk_widget_hide(g_btn_next); /* 2. move close button to the last position */ gtk_box_set_child_packing(g_box_buttons, g_btn_close, false, false, 5, GTK_PACK_END); } static void show_next_step_button() { gtk_box_set_child_packing(g_box_buttons, g_btn_close, false, false, 5, GTK_PACK_START); gtk_widget_show(g_btn_next); } enum { TERMINATE_NOFLAGS = 0, TERMINATE_WITH_RERUN = 1 << 0, }; static void terminate_event_chain(int flags) { hide_next_step_button(); if ((flags & TERMINATE_WITH_RERUN)) return; g_clear_pointer(&g_event_selected, free); g_list_free_full(g_auto_event_list, free); g_auto_event_list = NULL; } static void cancel_processing(GtkLabel *status_label, const char *message, int terminate_flags) { PangoAttribute *attr; PangoAttrList *list; attr = pango_attr_weight_new(PANGO_WEIGHT_BOLD); list = pango_attr_list_new(); pango_attr_list_insert(list, attr); gtk_label_set_attributes(status_label, list); gtk_label_set_text(status_label, message ? message : _("Processing was canceled")); terminate_event_chain(terminate_flags); pango_attr_list_unref(list); } static void update_command_run_log(const char *message, struct analyze_event_data *evd) { const bool it_is_a_dot = (message[0] == '.' && message[1] == '\0'); if (!it_is_a_dot) gtk_label_set_text(g_lbl_event_log, message); /* Don't append new line behind single dot */ char *log_msg = it_is_a_dot ? (char *)message : g_strdup_printf("%s\n", message); append_to_textview(g_tv_event_log, log_msg); save_to_event_log(evd, log_msg); if (log_msg != message) { /* We assume message is managed by the caller. */ free(log_msg); } } static void run_event_gtk_error(const char *error_line, void *param) { update_command_run_log(error_line, (struct analyze_event_data *)param); } static char *run_event_gtk_logging(char *log_line, void *param) { struct analyze_event_data *evd = (struct analyze_event_data *)param; update_command_run_log(log_line, evd); return log_line; } static void log_request_response_communication(const char *request, const char *response, struct analyze_event_data *evd) { g_autofree char *message = g_strdup_printf(response ? "%s '%s'" : "%s", request, response); update_command_run_log(message, evd); } static void run_event_gtk_alert(const char *msg, void *args) { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_wnd_assistant), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, "%s", msg); g_autofree char *tagged_msg = tag_url(msg, "\n"); gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dialog), tagged_msg); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); log_request_response_communication(msg, NULL, (struct analyze_event_data *)args); } static void gtk_entry_emit_dialog_response_ok(GtkEntry *entry, GtkDialog *dialog) { /* Don't close the dialogue if the entry is empty */ if (gtk_entry_get_text_length(entry) > 0) gtk_dialog_response(dialog, GTK_RESPONSE_OK); } static char *ask_helper(const char *msg, void *args, bool password) { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_wnd_assistant), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, "%s", msg); g_autofree char *tagged_msg = tag_url(msg, "\n"); gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_OK); gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dialog), tagged_msg); GtkWidget *vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog)); GtkWidget *textbox = gtk_entry_new(); /* gtk_entry_set_editable(GTK_ENTRY(textbox), TRUE); * is not available in gtk3, so please use the highlevel * g_object_set */ g_object_set(G_OBJECT(textbox), "editable", TRUE, NULL); g_signal_connect(textbox, "activate", G_CALLBACK(gtk_entry_emit_dialog_response_ok), dialog); if (password) gtk_entry_set_visibility(GTK_ENTRY(textbox), FALSE); gtk_box_pack_start(GTK_BOX(vbox), textbox, TRUE, TRUE, 0); gtk_widget_show(textbox); char *response = NULL; if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_OK) { const char *text = gtk_entry_get_text(GTK_ENTRY(textbox)); response = g_strdup(text); } gtk_widget_destroy(textbox); gtk_widget_destroy(dialog); const char *log_response = ""; if (response) log_response = password ? "********" : response; log_request_response_communication(msg, log_response, (struct analyze_event_data *)args); return response ? response : g_strdup(""); } static char *run_event_gtk_ask(const char *msg, void *args) { return ask_helper(msg, args, false); } static int run_event_gtk_ask_yes_no(const char *msg, void *args) { GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(g_wnd_assistant), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s", msg); g_autofree char *tagged_msg = tag_url(msg, "\n"); gtk_message_dialog_set_markup(GTK_MESSAGE_DIALOG(dialog), tagged_msg); /* Esc -> No, Enter -> Yes */ gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES); const int ret = gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES; gtk_widget_destroy(dialog); log_request_response_communication(msg, ret ? "YES" : "NO", (struct analyze_event_data *)args); return ret; } static int run_event_gtk_ask_yes_no_yesforever(const char *key, const char *msg, void *args) { const int ret = libreport_run_ask_yes_no_yesforever_dialog(key, msg, GTK_WINDOW(g_wnd_assistant)); log_request_response_communication(msg, ret ? "YES" : "NO", (struct analyze_event_data *)args); return ret; } static int run_event_gtk_ask_yes_no_save_result(const char *key, const char *msg, void *args) { const int ret = libreport_run_ask_yes_no_save_result_dialog(key, msg, GTK_WINDOW(g_wnd_assistant)); log_request_response_communication(msg, ret ? "YES" : "NO", (struct analyze_event_data *)args); return ret; } static char *run_event_gtk_ask_password(const char *msg, void *args) { return ask_helper(msg, args, true); } static bool event_need_review(const char *event_name) { event_config_t *event_cfg = get_event_config(event_name); return !event_cfg || !event_cfg->ec_skip_review; } static gint select_next_page_no(gint current_page_no); static void on_page_prepare(GtkNotebook *assistant, GtkWidget *page, gpointer user_data); static void on_btn_repeat_cb(GtkButton *button) { g_auto_event_list = g_list_prepend(g_auto_event_list, g_event_selected); g_event_selected = NULL; show_next_step_button(); clear_warnings(); const gint current_page_no = gtk_notebook_get_current_page(g_assistant); const int next_page_no = select_next_page_no(PAGENO_SUMMARY); if (current_page_no == next_page_no) on_page_prepare(g_assistant, gtk_notebook_get_nth_page(g_assistant, next_page_no), NULL); else gtk_notebook_set_current_page(g_assistant, next_page_no); } static void on_failed_event(const char *event_name) { add_warning(_("Processing of the problem failed. This can have many reasons but there are three most common:\n"\ "\t▫ <b>network connection problems</b>\n"\ "\t▫ <b>corrupted problem data</b>\n"\ "\t▫ <b>invalid configuration</b>"), GTK_CONTAINER(g_report_warning_label_box)); add_warning(_("If you want to update the configuration and try to report again, please open <b>Preferences</b> item\n" "in the application menu and after applying the configuration changes click <b>Repeat</b> button."), GTK_CONTAINER(g_report_warning_label_box)); gtk_widget_show(g_btn_repeat); gtk_widget_show(g_report_warning_label_box); } static bool event_requires_details(const char *event_name) { event_config_t *cfg = get_event_config(event_name); return cfg != NULL && cfg->ec_requires_details; } static gboolean consume_cmd_output(GIOChannel *source, GIOCondition condition, gpointer data) { struct analyze_event_data *evd = data; struct run_event_state *run_state = evd->run_state; bool stop_requested = false; int retval = consume_event_command_output(run_state, g_dump_dir_name); if (retval < 0 && errno == EAGAIN) /* We got all buffered data, but fd is still open. Done for now */ return TRUE; /* "please don't remove this event (yet)" */ /* EOF/error */ if (WIFEXITED(run_state->process_status) && WEXITSTATUS(run_state->process_status) == EXIT_STOP_EVENT_RUN ) { retval = 0; run_state->process_status = 0; stop_requested = true; terminate_event_chain(TERMINATE_NOFLAGS); } unexport_event_config(evd->env_list); evd->env_list = NULL; /* Make sure "Cancel" button won't send anything (process is gone) */ g_event_child_pid = -1; evd->run_state->command_pid = -1; /* just for consistency */ /* Write a final message to the log */ if (evd->event_log->len != 0 && evd->event_log->str[evd->event_log->len - 1] != '\n') save_to_event_log(evd, "\n"); /* If program failed, or if it finished successfully without saying anything... */ if (retval != 0 || evd->event_log_state == LOGSTATE_FIRSTLINE) { g_autofree char *msg = libreport_exit_status_as_string(evd->event_name, run_state->process_status); if (retval != 0) { /* If program failed, emit *error* line */ evd->event_log_state = LOGSTATE_ERRLINE; } append_to_textview(g_tv_event_log, msg); save_to_event_log(evd, msg); } /* Append log to FILENAME_EVENT_LOG */ update_event_log_on_disk(evd->event_log->str); g_string_erase(evd->event_log, 0, -1); evd->event_log_state = LOGSTATE_FIRSTLINE; struct dump_dir *dd = NULL; if (geteuid() == 0) { /* Reset mode/uig/gid to correct values for all files created by event run */ dd = dd_opendir(g_dump_dir_name, 0); if (dd) dd_sanitize_mode_and_owner(dd); } if (retval == 0 && libreport_get_global_stop_on_not_reportable()) { /* Check whether NOT_REPORTABLE element appeared. If it did, we'll stop * even if exit code is "success". */ if (!dd) /* why? because dd may be already open by the code above */ dd = dd_opendir(g_dump_dir_name, DD_OPEN_READONLY | DD_FAIL_QUIETLY_EACCES); if (!dd) libreport_xfunc_die(); g_autofree char *not_reportable = dd_load_text_ext(dd, FILENAME_NOT_REPORTABLE, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_FAIL_QUIETLY_ENOENT | DD_FAIL_QUIETLY_EACCES); if (not_reportable) retval = 256; } if (dd) dd_close(dd); /* Stop if exit code is not 0, or no more commands */ if (stop_requested || retval != 0 || spawn_next_command_in_evd(evd) < 0 ) { log_notice("done running event on '%s': %d", g_dump_dir_name, retval); append_to_textview(g_tv_event_log, "\n"); /* Hide spinner and stop btn */ gtk_widget_hide(GTK_WIDGET(g_spinner_event_log)); gtk_widget_hide(g_btn_stop); /* Enable (un-gray out) navigation buttons */ gtk_widget_set_sensitive(g_btn_close, true); gtk_widget_set_sensitive(g_btn_next, true); problem_data_reload_from_dump_dir(); update_gui_state_from_problem_data(UPDATE_SELECTED_EVENT); if (retval != 0) { gtk_widget_show(GTK_WIDGET(g_img_process_fail)); /* 256 means NOT_REPORTABLE */ if (retval == 256) cancel_processing(g_lbl_event_log, _("Processing was interrupted because the problem is not reportable."), TERMINATE_NOFLAGS); else { /* We use SIGTERM to stop event processing on user's request. * So SIGTERM is not a failure. */ if (retval == EXIT_CANCEL_BY_USER || WTERMSIG(run_state->process_status) == SIGTERM) cancel_processing(g_lbl_event_log, /* default message */ NULL, TERMINATE_NOFLAGS); else { cancel_processing(g_lbl_event_log, _("Processing failed."), TERMINATE_WITH_RERUN); on_failed_event(evd->event_name); } } } else { gtk_label_set_text(g_lbl_event_log, is_processing_finished() ? _("Processing finished.") : _("Processing finished, please proceed to the next step.")); } g_source_remove(g_event_source_id); g_event_source_id = 0; close(evd->fd); g_io_channel_unref(evd->channel); g_string_free(evd->event_log, TRUE); evd->event_log = NULL; free_run_event_state(evd->run_state); g_clear_pointer(&evd->event_name, g_free); g_clear_pointer(&evd, g_free); /* Inform abrt-gui that it is a good idea to rescan the directory */ kill(getppid(), SIGCHLD); if (is_processing_finished()) hide_next_step_button(); else if (retval == 0 && !libreport_g_verbose) on_next_btn_cb(g_btn_next, NULL); return FALSE; /* "please remove this event" */ } /* New command was started. Continue waiting for input */ /* Transplant cmd's output fd onto old one, so that main loop * is none the wiser that fd it waits on has changed */ libreport_xmove_fd(evd->run_state->command_out_fd, evd->fd); evd->run_state->command_out_fd = evd->fd; /* just to keep it consistent */ libreport_ndelay_on(evd->fd); /* Revive "Cancel" button */ g_event_child_pid = evd->run_state->command_pid; return TRUE; /* "please don't remove this event (yet)" */ } static void start_event_run(const char *event_name) { /* Start event asynchronously on the dump dir * (synchronous run would freeze GUI until completion) */ struct run_event_state *state = new_run_event_state(); state->logging_callback = run_event_gtk_logging; state->error_callback = run_event_gtk_error; state->alert_callback = run_event_gtk_alert; state->ask_callback = run_event_gtk_ask; state->ask_yes_no_callback = run_event_gtk_ask_yes_no; state->ask_yes_no_yesforever_callback = run_event_gtk_ask_yes_no_yesforever; state->ask_yes_no_save_result_callback = run_event_gtk_ask_yes_no_save_result; state->ask_password_callback = run_event_gtk_ask_password; if (prepare_commands(state) == 0) { /* Strangely, no commands at all were found for processing the crashdump. * Let the user know and terminate processing. */ free_run_event_state(state); log_warning("No processing commands specified. Processing halted."); g_autofree char *msg = g_strdup_printf( _("No commands could be found for processsing the crashdump.\n")); append_to_textview(g_tv_event_log, msg); cancel_processing(g_lbl_event_log, _("Processing failed."), TERMINATE_NOFLAGS); return; } struct dump_dir *dd = wizard_open_directory_for_writing(g_dump_dir_name); dd_close(dd); if (!dd) { free_run_event_state(state); cancel_processing(g_lbl_event_log, _("Processing interrupted: can't continue without writable directory."), TERMINATE_NOFLAGS); return; /* user refused to steal, or write error, etc... */ } if (tv_details_shown) set_excluded_envvar(); GList *env_list = export_event_config(event_name); int retval = spawn_next_command(state, g_dump_dir_name, event_name, EXECFLG_SETPGID); log_notice("Return value of %s was %d", event_name, retval); if (retval < 0) { /* No commands were run for this event because none matching were found. * Acknowledge this fact and continue with processing. */ assert(state->children_count == 0); unexport_event_config(env_list); free_run_event_state(state); log_notice("No matching actions for event '%s'; skipping.", event_name); g_autofree char *msg = g_strdup_printf( _("--- Skipping %s ---\n" "No matching actions found for this event.\n\n"), event_name); append_to_textview(g_tv_event_log, msg); if (is_processing_finished()) hide_next_step_button(); return; } g_event_child_pid = state->command_pid; /* At least one command is needed, and we started first one. * Hook its output fd to the main loop. */ struct analyze_event_data *evd = g_new0(struct analyze_event_data, 1); evd->run_state = state; evd->event_name = g_strdup(event_name); evd->env_list = env_list; evd->event_log = g_string_new(NULL); evd->fd = state->command_out_fd; state->logging_param = evd; state->error_param = evd; state->interaction_param = evd; libreport_ndelay_on(evd->fd); evd->channel = g_io_channel_unix_new(evd->fd); g_event_source_id = g_io_add_watch(evd->channel, G_IO_IN | G_IO_ERR | G_IO_HUP, /* need HUP to detect EOF w/o any data */ consume_cmd_output, evd ); gtk_label_set_text(g_lbl_event_log, _("Processing...")); log_notice("running event '%s' on '%s'", event_name, g_dump_dir_name); g_autofree char *msg = g_strdup_printf("--- Running %s ---\n", event_name); append_to_textview(g_tv_event_log, msg); /* don't bother testing if they are visible, this is faster */ gtk_widget_hide(GTK_WIDGET(g_img_process_fail)); gtk_widget_show(GTK_WIDGET(g_spinner_event_log)); gtk_widget_show(g_btn_stop); /* Disable (gray out) navigation buttons */ gtk_widget_set_sensitive(g_btn_close, false); gtk_widget_set_sensitive(g_btn_next, false); } /* * the widget is added as a child of the VBox in the warning area * */ static void add_widget_to_warning_area(GtkWidget *widget, GtkContainer *container) { g_warning_issued = true; gtk_container_add(container, widget); gtk_widget_show_all(widget); } /* Backtrace checkbox handling */ static void add_warning(const char *warning, GtkContainer *container) { g_autofree char *label_str = g_strdup_printf("• %s", warning); GtkWidget *warning_lbl = gtk_label_new(NULL); gtk_label_set_markup(GTK_LABEL(warning_lbl), label_str); gtk_widget_set_halign (warning_lbl, GTK_ALIGN_START); gtk_widget_set_valign (warning_lbl, GTK_ALIGN_END); gtk_label_set_justify(GTK_LABEL(warning_lbl), GTK_JUSTIFY_LEFT); gtk_label_set_ellipsize(GTK_LABEL(warning_lbl), PANGO_ELLIPSIZE_END); add_widget_to_warning_area(warning_lbl, container); } static void on_sensitive_ticket_clicked_cb(GtkWidget *button, gpointer user_data) { libreport_set_global_create_private_ticket(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)), /*transient*/0); } static void on_privacy_info_btn(GtkWidget *button, gpointer user_data) { if (g_event_selected == NULL) return; libreport_show_event_config_dialog(g_event_selected, GTK_WINDOW(g_top_most_window)); } static void private_ticket_creation_warning(int flags) { if (flags & PRIV_WARN_HIDE_BTN) { gtk_widget_hide(GTK_WIDGET(g_sens_ticket)); } if (flags & PRIV_WARN_SHOW_BTN) { gtk_widget_show_all(GTK_WIDGET(g_sens_ticket)); gtk_widget_show(GTK_WIDGET(g_sens_ticket)); } if (flags & PRIV_WARN_BTN_UNCHECKED) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g_sens_ticket_cb), FALSE); if (flags & PRIV_WARN_BTN_CHECKED) gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g_sens_ticket_cb), TRUE); if (flags & PRIV_WARN_HIDE_MSG) clear_warnings(); if (flags & PRIV_WARN_SHOW_MSG) { add_warning(_("Possible sensitive data detected, feel free to edit the report and remove them."), GTK_CONTAINER(g_box_warning_labels)); show_warnings(); } } static void add_sensitive_data_warning(void) { int flags = PRIV_WARN_SHOW_MSG; event_config_t *cfg = get_event_config(g_event_selected); if (cfg != NULL && cfg->ec_supports_restricted_access) flags |= PRIV_WARN_SHOW_BTN | PRIV_WARN_BTN_CHECKED; private_ticket_creation_warning(flags); } static void show_warnings(void) { if (g_warning_issued) gtk_widget_show(g_widget_warnings_area); } static void clear_warnings(void) { /* erase all warnings */ if (!g_warning_issued) return; gtk_widget_hide(g_widget_warnings_area); gtk_container_foreach(GTK_CONTAINER(g_box_warning_labels), &remove_child_widget, NULL); gtk_container_foreach(GTK_CONTAINER(g_report_warning_label_box), &remove_child_widget, NULL); gtk_widget_hide(g_report_warning_label_box); g_warning_issued = false; } static bool check_minimal_bt_rating(const char *event_name, char **warning) { bool acceptable_rating = true; event_config_t *event_cfg = NULL; if (!event_name) error_msg_and_die(_("Cannot check backtrace rating because of invalid event name")); else if (!g_str_has_prefix(event_name, "report")) { log_info("No checks for backtrace rating because event '%s' doesn't report.", event_name); return acceptable_rating; } else event_cfg = get_event_config(event_name); acceptable_rating = check_problem_rating_usability(event_cfg, g_cd, warning, NULL); return acceptable_rating; } static void on_bt_approve_toggle(GtkToggleButton *togglebutton, gpointer user_data) { gtk_widget_set_sensitive(g_btn_next, gtk_toggle_button_get_active(g_tb_approve_bt)); } static void toggle_eb_comment(void) { /* The page doesn't exist with report-only option */ if (pages[PAGENO_EDIT_COMMENT].page_widget == NULL) return; bool complex_details = g_event_selected && event_requires_details(g_event_selected); bool good = false; if (complex_details) { int reproducible = gtk_combo_box_get_active(GTK_COMBO_BOX(g_cmb_reproducible)); const int comment_chars = gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(g_tv_comment)); const int steps_chars = gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(g_tv_steps)); const int steps_lines = steps_chars == 0 ? 0 : gtk_text_buffer_get_line_count(gtk_text_view_get_buffer(g_tv_steps)); switch(reproducible) { case -1: VERB1 log_warning("Uninitialized 'How reproducible' combobox"); break; case PROBLEM_REPRODUCIBLE_UNKNOWN: good = comment_chars + (steps_chars * 2) >= 20; break; case PROBLEM_REPRODUCIBLE_YES: good = comment_chars >= 10 && steps_lines; break; case PROBLEM_REPRODUCIBLE_RECURRENT: good = comment_chars >= 10 || steps_lines; break; default: error_msg("BUG: %s:%s:%d: forgotten 'how reproducible' value", __FILE__, __func__, __LINE__); break; } } else { good = gtk_text_buffer_get_char_count(gtk_text_view_get_buffer(g_tv_comment)) >= 10 || gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(g_cb_no_comment)); /* And show the eventbox with label */ if (good) gtk_widget_hide(GTK_WIDGET(g_eb_comment)); else gtk_widget_show(GTK_WIDGET(g_eb_comment)); } /* Allow next page only when the comment has at least 10 chars */ gtk_widget_set_sensitive(g_btn_next, good); } static void on_comment_changed(GtkTextBuffer *buffer, gpointer user_data) { toggle_eb_comment(); } static void on_no_comment_toggled(GtkToggleButton *togglebutton, gpointer user_data) { toggle_eb_comment(); } static void on_steps_changed(GtkTextBuffer *buffer, gpointer user_data) { toggle_eb_comment(); } static void on_log_changed(GtkTextBuffer *buffer, gpointer user_data) { gtk_widget_show(GTK_WIDGET(g_exp_report_log)); } static GList *find_words_in_text_buffer(int page, GtkTextView *tev, GList *words, GList *ignore_sitem_list, GtkTextIter start_find, GtkTextIter end_find, bool case_insensitive ) { GtkTextBuffer *buffer = gtk_text_view_get_buffer(tev); gtk_text_buffer_set_modified(buffer, FALSE); GList *found_words = NULL; GtkTextIter start_match; GtkTextIter end_match; for (GList *w = words; w; w = g_list_next(w)) { gtk_text_buffer_get_start_iter(buffer, &start_find); const char *search_word = w->data; while (search_word && search_word[0] && gtk_text_iter_forward_search(&start_find, search_word, GTK_TEXT_SEARCH_TEXT_ONLY | (case_insensitive ? GTK_TEXT_SEARCH_CASE_INSENSITIVE : 0), &start_match, &end_match, NULL)) { search_item_t *found_word = sitem_new( page, buffer, tev, start_match, end_match ); int offset = gtk_text_iter_get_offset(&end_match); gtk_text_buffer_get_iter_at_offset(buffer, &start_find, offset); if (g_list_find_custom(ignore_sitem_list, found_word, (GCompareFunc)sitem_contains)) { g_clear_pointer(&found_word, g_free); // don't count the word if it's part of some of the ignored words continue; } found_words = g_list_prepend(found_words, found_word); } } return found_words; } static void search_item_to_list_store_item(GtkListStore *store, GtkTreeIter *new_row, const gchar *file_name, search_item_t *word) { GtkTextIter *beg = gtk_text_iter_copy(&(word->start)); gtk_text_iter_backward_line(beg); GtkTextIter *end = gtk_text_iter_copy(&(word->end)); /* the first call moves end variable at the end of the current line */ if (gtk_text_iter_forward_line(end)) { /* the second call moves end variable at the end of the next line */ gtk_text_iter_forward_line(end); /* don't include the last new which causes an empty line in the GUI list */ gtk_text_iter_backward_char(end); } gchar *tmp = gtk_text_buffer_get_text(word->buffer, beg, &(word->start), /*don't include hidden chars*/FALSE); g_autofree gchar *prefix = g_markup_escape_text(tmp, /*NULL terminated string*/-1); g_free(tmp); tmp = gtk_text_buffer_get_text(word->buffer, &(word->start), &(word->end), /*don't include hidden chars*/FALSE); g_autofree gchar *text = g_markup_escape_text(tmp, /*NULL terminated string*/-1); g_free(tmp); tmp = gtk_text_buffer_get_text(word->buffer, &(word->end), end, /*don't include hidden chars*/FALSE); g_autofree gchar *suffix = g_markup_escape_text(tmp, /*NULL terminated string*/-1); g_clear_pointer(&tmp, g_free); char *content = g_strdup_printf("%s<span foreground=\"red\">%s</span>%s", prefix, text, suffix); gtk_text_iter_free(end); gtk_text_iter_free(beg); gtk_list_store_set(store, new_row, SEARCH_COLUMN_FILE, file_name, SEARCH_COLUMN_TEXT, content, SEARCH_COLUMN_ITEM, word, -1); } static bool highlight_words_in_textview(int page, GtkTextView *tev, GList *words, GList *ignored_words, bool case_insensitive) { GtkTextBuffer *buffer = gtk_text_view_get_buffer(tev); gtk_text_buffer_set_modified(buffer, FALSE); GtkWidget *notebook_child = gtk_notebook_get_nth_page(g_notebook, page); GtkWidget *tab_lbl = gtk_notebook_get_tab_label(g_notebook, notebook_child); /* Remove old results */ bool buffer_removing = false; GtkTreeIter iter; gboolean valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_ls_sensitive_list), &iter); /* Turn off the changed callback during the update */ g_signal_handler_block(g_tv_sensitive_sel, g_tv_sensitive_sel_hndlr); while (valid) { g_autofree char *text = NULL; search_item_t *word = NULL; gtk_tree_model_get(GTK_TREE_MODEL(g_ls_sensitive_list), &iter, SEARCH_COLUMN_TEXT, &text, SEARCH_COLUMN_ITEM, &word, -1); if (word->buffer == buffer) { buffer_removing = true; valid = gtk_list_store_remove(g_ls_sensitive_list, &iter); if (word == g_current_highlighted_word) g_current_highlighted_word = NULL; free(word); } else { if(buffer_removing) break; valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(g_ls_sensitive_list), &iter); } } /* Turn on the changed callback after the update */ g_signal_handler_unblock(g_tv_sensitive_sel, g_tv_sensitive_sel_hndlr); GtkTextIter start_find; gtk_text_buffer_get_start_iter(buffer, &start_find); GtkTextIter end_find; gtk_text_buffer_get_end_iter(buffer, &end_find); gtk_text_buffer_remove_tag_by_name(buffer, "search_result_bg", &start_find, &end_find); gtk_text_buffer_remove_tag_by_name(buffer, "current_result_bg", &start_find, &end_find); PangoAttrList *attrs = gtk_label_get_attributes(GTK_LABEL(tab_lbl)); gtk_label_set_attributes(GTK_LABEL(tab_lbl), NULL); pango_attr_list_unref(attrs); GList *result = NULL; GList *ignored_words_in_buffer = NULL; ignored_words_in_buffer = find_words_in_text_buffer(page, tev, ignored_words, NULL, start_find, end_find, /*case sensitive*/false); result = find_words_in_text_buffer(page, tev, words, ignored_words_in_buffer, start_find, end_find, case_insensitive ); for (GList *w = result; w; w = g_list_next(w)) { search_item_t *item = (search_item_t *)w->data; gtk_text_buffer_apply_tag_by_name(buffer, "search_result_bg", sitem_get_start_iter(item), sitem_get_end_iter(item)); } if (result) { PangoAttrList *attrs = pango_attr_list_new(); PangoAttribute *foreground_attr = pango_attr_foreground_new(65535, 0, 0); pango_attr_list_insert(attrs, foreground_attr); PangoAttribute *underline_attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE); pango_attr_list_insert(attrs, underline_attr); gtk_label_set_attributes(GTK_LABEL(tab_lbl), attrs); /* The current order of the found words is defined by order of words in the * passed list. We have to order the words according to their occurrence in * the buffer. */ result = g_list_sort(result, (GCompareFunc)sitem_compare); GList *search_result = result; for ( ; search_result != NULL; search_result = g_list_next(search_result)) { search_item_t *word = (search_item_t *)search_result->data; const gchar *file_name = gtk_label_get_text(GTK_LABEL(tab_lbl)); /* Create a new row */ GtkTreeIter new_row; if (valid) /* iter variable is valid GtkTreeIter and it means that the results */ /* need to be inserted before this iterator, in this case iter points */ /* to the first word of another GtkTextView */ gtk_list_store_insert_before(g_ls_sensitive_list, &new_row, &iter); else /* the GtkTextView is the last one or the only one, insert the results */ /* at the end of the list store */ gtk_list_store_append(g_ls_sensitive_list, &new_row); /* Assign values to the new row */ search_item_to_list_store_item(g_ls_sensitive_list, &new_row, file_name, word); } } g_list_free_full(ignored_words_in_buffer, free); g_list_free(result); return result != NULL; } static gboolean highligh_words_in_tabs(GList *forbidden_words, GList *allowed_words, bool case_insensitive, bool is_custom_search) { gboolean found = false; GList *ignored_elements = libreport_load_words_from_file(ELEMENT_IGNORE_LIST); gint n_pages = gtk_notebook_get_n_pages(g_notebook); int page = 0; for (page = 0; page < n_pages; page++) { //notebook_page->scrolled_window->text_view GtkWidget *notebook_child = gtk_notebook_get_nth_page(g_notebook, page); GtkWidget *tab_lbl = gtk_notebook_get_tab_label(g_notebook, notebook_child); // ignore some of the elements, but only if this is not a custom search (user typing in the UI) if (!is_custom_search) { const gchar* element_name = gtk_notebook_get_tab_label_text(g_notebook, notebook_child); if (g_list_find_custom(ignored_elements, element_name, (GCompareFunc) strcmp)) { // this element is on the ignore list, so let's not highlight anything here continue; } } const char *const lbl_txt = gtk_label_get_text(GTK_LABEL(tab_lbl)); if (strncmp(lbl_txt, "page 1", 5) == 0 || strcmp(FILENAME_COMMENT, lbl_txt) == 0) continue; GtkTextView *tev = GTK_TEXT_VIEW(gtk_bin_get_child(GTK_BIN(notebook_child))); found |= highlight_words_in_textview(page, tev, forbidden_words, allowed_words, case_insensitive); } g_list_free_full(ignored_elements, free); GtkTreeIter iter; if (gtk_tree_model_get_iter_first(GTK_TREE_MODEL(g_ls_sensitive_list), &iter)) gtk_tree_selection_select_iter(g_tv_sensitive_sel, &iter); return found; } static gboolean highlight_forbidden(void) { GList *forbidden_words = libreport_load_words_from_file(FORBIDDEN_WORDS_BLACKLLIST); GList *allowed_words = libreport_load_words_from_file(FORBIDDEN_WORDS_WHITELIST); const gboolean result = highligh_words_in_tabs(forbidden_words, allowed_words, /*case sensitive*/false, /*is_custom_search*/false); g_list_free_full(allowed_words, free); g_list_free_full(forbidden_words, free); return result; } static char *get_next_processed_event(GList **events_list) { if (!events_list || !*events_list) return NULL; char *event_name = (char *)(*events_list)->data; *events_list = g_list_delete_link(*events_list, *events_list); clear_warnings(); char *warning = NULL; const bool acceptable = check_minimal_bt_rating(event_name, &warning); if (warning != NULL) { add_warning(warning, GTK_CONTAINER(g_report_warning_label_box)); g_clear_pointer(&warning, g_free); gtk_widget_show(g_report_warning_label_box); } if (!acceptable) { /* a node for this event was already removed */ free(event_name); g_list_free_full(*events_list, free); *events_list = NULL; return NULL; } return event_name; } static void update_private_ticket_creation_warning_for_selected_event(void) { event_config_t *cfg = get_event_config(g_event_selected); if (cfg == NULL || !cfg->ec_supports_restricted_access) return; int flags = PRIV_WARN_SHOW_BTN | PRIV_WARN_HIDE_MSG; if (ec_restricted_access_enabled(cfg)) flags |= PRIV_WARN_BTN_CHECKED; private_ticket_creation_warning(flags); } static void on_page_prepare(GtkNotebook *assistant, GtkWidget *page, gpointer user_data) { /* If processing is finished and if it was terminated because of an error * the event progress page is selected. So, it does not make sense to show * the next step button and we MUST NOT clear warnings. */ if (!is_processing_finished()) { /* Some pages hide it, so restore it to its default. */ show_next_step_button(); clear_warnings(); } gtk_widget_hide(g_btn_detail); gtk_widget_hide(g_btn_repeat); /* Save text fields if changed */ /* Must be called before any GUI operation because the following two * functions causes recreating of the text items tabs, thus all updates to * these tabs will be lost */ save_items_from_notepad(); save_text_from_text_view(g_tv_comment, FILENAME_COMMENT); bool complex_details = g_event_selected && event_requires_details(g_event_selected); if (complex_details) { save_text_from_text_view(g_tv_steps, FILENAME_REPRODUCER); int reproducible = gtk_combo_box_get_active(GTK_COMBO_BOX(g_cmb_reproducible)); if (reproducible > -1) { const char *reproducible_str = get_problem_data_reproducible_name(reproducible); if (reproducible_str != NULL) { struct dump_dir *dd = wizard_open_directory_for_writing(g_dump_dir_name); if (dd) dd_save_text(dd, FILENAME_REPRODUCIBLE, reproducible_str); else error_msg(_("Failed to save file '%s'"), FILENAME_REPRODUCIBLE); dd_close(dd); } } } problem_data_reload_from_dump_dir(); update_gui_state_from_problem_data(/* don't update selected event */ 0); if (pages[PAGENO_SUMMARY].page_widget == page) { if (libreport_get_global_create_private_ticket()) private_ticket_creation_warning( PRIV_WARN_SHOW_BTN | PRIV_WARN_BTN_CHECKED | PRIV_WARN_HIDE_MSG); /* Skip intro screen */ int n = select_next_page_no(PAGENO_SUMMARY); log_info("Switching from intro to page no. %d", n); gtk_notebook_set_current_page(assistant, n); return; } if (pages[PAGENO_EDIT_ELEMENTS].page_widget == page) { if (highlight_forbidden()) { add_sensitive_data_warning(); show_warnings(); gtk_expander_set_expanded(g_exp_search, TRUE); } else gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(g_rb_custom_search), TRUE); show_warnings(); } if (pages[PAGENO_REVIEW_DATA].page_widget == page) { update_ls_details_checkboxes(g_event_selected); gtk_widget_set_sensitive(g_btn_next, gtk_toggle_button_get_active(g_tb_approve_bt)); } if (pages[PAGENO_EDIT_COMMENT].page_widget == page) { bool complex_details = g_event_selected && event_requires_details(g_event_selected); gtk_widget_set_visible(GTK_WIDGET(g_vb_simple_details), !complex_details); gtk_widget_set_visible(GTK_WIDGET(g_vb_complex_details), complex_details); update_private_ticket_creation_warning_for_selected_event(); gtk_widget_show(g_btn_detail); gtk_widget_set_sensitive(g_btn_next, false); on_comment_changed(gtk_text_view_get_buffer(g_tv_comment), NULL); } if (pages[PAGENO_EVENT_PROGRESS].page_widget == page) { while (g_event_selected && g_event_selected[0]) { log_notice("Selected event '%s'; going to run its actions now...", g_event_selected); clear_warnings(); start_event_run(g_event_selected); /* Automatically proceed with the next event or page if * - no command was spawned by start_event_run, * - there are events still left to be run, and * - we're in cruise control mode (i.e. not verbose). */ if (g_event_child_pid < 0 && !is_processing_finished() && !libreport_g_verbose) { gint next_page_no = select_next_page_no(PAGENO_EVENT_PROGRESS); /* If the page doesn't change, the switch-page signal is not * emitted and on_page_prepare() is not called. Hence we jump back * and continue as if this function were called in the updated * environment. */ if (next_page_no == PAGENO_EVENT_PROGRESS) continue; gtk_notebook_set_current_page(g_assistant, next_page_no); } break; } } if (pages[PAGENO_EVENT_SELECTOR].page_widget == page) { if (is_processing_finished()) hide_next_step_button(); } } static void set_auto_event_chain(GtkButton *button, gpointer user_data) { workflow_t *w = (workflow_t *)user_data; config_item_info_t *info = workflow_get_config_info(w); log_notice("selected workflow '%s'", ci_get_screen_name(info)); GList *wf_event_list = wf_get_event_list(w); while(wf_event_list) { g_auto_event_list = g_list_append(g_auto_event_list, g_strdup(ec_get_name(wf_event_list->data))); libreport_load_single_event_config_data_from_user_storage((event_config_t *)wf_event_list->data); wf_event_list = g_list_next(wf_event_list); } gint current_page_no = gtk_notebook_get_current_page(g_assistant); gint next_page_no = select_next_page_no(current_page_no); /* If page is not changed, 'switch-page' signal is not emitted. */ if (current_page_no == next_page_no) on_page_prepare(g_assistant, gtk_notebook_get_nth_page(g_assistant, next_page_no), NULL); else gtk_notebook_set_current_page(g_assistant, next_page_no); /* Show Next Step button which was hidden on Selector page in non-expert * mode. Next Step button must be hidden because Selector page shows only * workflow buttons in non-expert mode. */ show_next_step_button(); } static void add_workflow_buttons(GtkBox *box, GHashTable *workflows, GCallback func) { gtk_container_foreach(GTK_CONTAINER(box), &remove_child_widget, NULL); GList *possible_workflows = list_possible_events_glist(g_dump_dir_name, "workflow"); GHashTable *workflow_table = load_workflow_config_data_from_list( possible_workflows, WORKFLOWS_DIR); g_list_free_full(possible_workflows, free); g_object_set_data_full(G_OBJECT(box), "workflows", workflow_table, (GDestroyNotify)g_hash_table_destroy); GList *wf_list = g_hash_table_get_values(workflow_table); wf_list = g_list_sort(wf_list, (GCompareFunc)wf_priority_compare); for (GList *wf_iter = wf_list; wf_iter; wf_iter = g_list_next(wf_iter)) { workflow_t *w = (workflow_t *)wf_iter->data; g_autofree char *btn_label = g_strdup_printf("<b>%s</b>\n%s", wf_get_screen_name(w), wf_get_description(w)); GtkWidget *button = gtk_button_new_with_label(btn_label); GList *children = gtk_container_get_children(GTK_CONTAINER(button)); GtkWidget *label = (GtkWidget *)children->data; gtk_label_set_use_markup(GTK_LABEL(label), true); gtk_widget_set_halign(label, GTK_ALIGN_START); gtk_widget_set_margin_top(label, 10); gtk_widget_set_margin_start(label, 40); gtk_widget_set_margin_bottom(label, 10); g_list_free(children); g_signal_connect(button, "clicked", func, w); gtk_box_pack_start(box, button, true, false, 2); } g_list_free(wf_list); } static char *setup_next_processed_event(GList **events_list) { g_clear_pointer(&g_event_selected, free); char *event = get_next_processed_event(events_list); if (!event) { /* No next event, go to progress page and finish. */ gtk_label_set_text(g_lbl_event_log, _("Processing finished.")); /* We don't know the result of the previous event here * so at least hide the spinner, because we're obviously finished. */ gtk_widget_hide(GTK_WIDGET(g_spinner_event_log)); hide_next_step_button(); return NULL; } log_notice("selected -e EVENT:%s", event); return event; } static bool get_sensitive_data_permission(const char *event_name) { event_config_t *event_cfg = get_event_config(event_name); if (!event_cfg || !event_cfg->ec_sending_sensitive_data) return true; const char *screen_name = ec_get_screen_name(event_cfg); if (screen_name) event_name = screen_name; g_autofree char *msg = g_strdup_printf( _("Event '%s' requires permission to send possibly sensitive data.\n" "Do you want to continue?"), event_name); const bool response = libreport_run_ask_yes_no_yesforever_dialog("ask_send_sensitive_data", msg, GTK_WINDOW(g_wnd_assistant)); return response; } static gint select_next_page_no(gint current_page_no) { GtkWidget *page; again: log_notice("%s: current_page_no:%d", __func__, current_page_no); current_page_no++; page = gtk_notebook_get_nth_page(g_assistant, current_page_no); if (pages[PAGENO_EVENT_SELECTOR].page_widget == page) { if (is_processing_finished()) { return current_page_no; //stay here and let user select the workflow } log_info("%s: Looking for next event to process", __func__); /* (note: this frees and sets to NULL g_event_selected) */ char *event = setup_next_processed_event(&g_auto_event_list); if (!event) { current_page_no = PAGENO_EVENT_PROGRESS - 1; goto again; } if (!get_sensitive_data_permission(event)) { free(event); cancel_processing(g_lbl_event_log, /* default message */ NULL, TERMINATE_NOFLAGS); current_page_no = PAGENO_EVENT_PROGRESS - 1; goto again; } if (problem_data_get_content_or_NULL(g_cd, FILENAME_NOT_REPORTABLE)) { g_autofree char *msg = g_strdup_printf(_("This problem should not be reported " "(it is likely a known problem). %s"), problem_data_get_content_or_NULL(g_cd, FILENAME_NOT_REPORTABLE) ); if (libreport_get_global_stop_on_not_reportable()) { free(event); cancel_processing(g_lbl_event_log, msg, TERMINATE_NOFLAGS); current_page_no = PAGENO_EVENT_PROGRESS - 1; goto again; } else { log_warning("%s", msg); } } g_event_selected = event; /* Notify the user that some configuration options miss values, but don't * force him to provide them. */ check_event_config(g_event_selected); current_page_no = PAGENO_EVENT_SELECTOR + 1; goto event_was_selected; } if (pages[PAGENO_EVENT_SELECTOR + 1].page_widget == page) { event_was_selected: if (!g_event_selected) { /* Go back to selectors */ current_page_no = PAGENO_EVENT_SELECTOR - 1; goto again; } if (!event_need_review(g_event_selected)) { current_page_no = PAGENO_EVENT_PROGRESS - 1; goto again; } } if (pages[PAGENO_EVENT_DONE].page_widget == page) { if (g_auto_event_list) { /* Go back to selectors */ current_page_no = PAGENO_SUMMARY; } goto again; } if (pages[PAGENO_NOT_SHOWN].page_widget == page) { exit(0); } log_notice("%s: selected page #%d", __func__, current_page_no); return current_page_no; } static void rehighlight_forbidden_words(int page, GtkTextView *tev) { GList *forbidden_words = libreport_load_words_from_file(FORBIDDEN_WORDS_BLACKLLIST); GList *allowed_words = libreport_load_words_from_file(FORBIDDEN_WORDS_WHITELIST); highlight_words_in_textview(page, tev, forbidden_words, allowed_words, /*case sensitive*/false); g_list_free_full(allowed_words, free); g_list_free_full(forbidden_words, free); } static void on_sensitive_word_selection_changed(GtkTreeSelection *sel, gpointer user_data) { search_item_t *old_word = g_current_highlighted_word; g_current_highlighted_word = NULL; if (old_word && FALSE == gtk_text_buffer_get_modified(old_word->buffer)) gtk_text_buffer_remove_tag_by_name(old_word->buffer, "current_result_bg", &(old_word->start), &(old_word->end)); GtkTreeModel *model; GtkTreeIter iter; if (!gtk_tree_selection_get_selected(sel, &model, &iter)) return; search_item_t *new_word; gtk_tree_model_get(model, &iter, SEARCH_COLUMN_ITEM, &new_word, -1); if (gtk_text_buffer_get_modified(new_word->buffer)) { if (g_search_text == NULL) rehighlight_forbidden_words(new_word->page, new_word->tev); else { log_notice("searching again: '%s'", g_search_text); GList *searched_words = g_list_append(NULL, (gpointer)g_search_text); highlight_words_in_textview(new_word->page, new_word->tev, searched_words, NULL, /*case insensitive*/true); g_list_free(searched_words); } return; } g_current_highlighted_word = new_word; gtk_notebook_set_current_page(g_notebook, new_word->page); gtk_text_buffer_apply_tag_by_name(new_word->buffer, "current_result_bg", &(new_word->start), &(new_word->end)); gtk_text_buffer_place_cursor(new_word->buffer, &(new_word->start)); gtk_text_view_scroll_to_iter(new_word->tev, &(new_word->start), 0.0, false, 0, 0); } static void highlight_search(GtkEntry *entry) { g_search_text = gtk_entry_get_text(entry); log_notice("searching: '%s'", g_search_text); GList *words = g_list_append(NULL, (gpointer)g_search_text); highligh_words_in_tabs(words, NULL, /*case insensitive*/true, /*is_custom_search*/true); g_list_free(words); } static gboolean highlight_search_on_timeout(gpointer user_data) { g_timeout = 0; highlight_search(GTK_ENTRY(user_data)); /* returning false will make glib to remove this event */ return false; } static void search_timeout(GtkEntry *entry) { /* this little hack makes the search start searching after 500 milisec after * user stops writing into entry box * if this part is removed, then the search will be started on every * change of the search entry */ if (g_timeout != 0) g_source_remove(g_timeout); g_timeout = g_timeout_add(500, &highlight_search_on_timeout, (gpointer)entry); } static void on_forbidden_words_toggled(GtkToggleButton *btn, gpointer user_data) { g_search_text = NULL; log_notice("nothing to search for, highlighting forbidden words instead"); highlight_forbidden(); } static void on_custom_search_toggled(GtkToggleButton *btn, gpointer user_data) { const gboolean custom_search = gtk_toggle_button_get_active(btn); gtk_widget_set_sensitive(GTK_WIDGET(g_search_entry_bt), custom_search); if (custom_search) highlight_search(g_search_entry_bt); } static void save_edited_one_liner(GtkCellRendererText *renderer, gchar *tree_path, gchar *new_text, gpointer user_data) { GtkTreeIter iter; if (!gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(g_ls_details), &iter, tree_path)) return; gchar *item_name = NULL; gtk_tree_model_get(GTK_TREE_MODEL(g_ls_details), &iter, DETAIL_COLUMN_NAME, &item_name, -1); if (!item_name) /* paranoia, should never happen */ return; struct problem_item *item = problem_data_get_item_or_NULL(g_cd, item_name); if (item && (item->flags & CD_FLAG_ISEDITABLE)) { struct dump_dir *dd = wizard_open_directory_for_writing(g_dump_dir_name); if (dd) { dd_save_text(dd, item_name, new_text); free(item->content); item->content = g_strdup(new_text); gtk_list_store_set(g_ls_details, &iter, DETAIL_COLUMN_VALUE, new_text, -1); } dd_close(dd); } } static void on_btn_add_file(GtkButton *button) { GtkWidget *dialog = gtk_file_chooser_dialog_new( "Attach File", GTK_WINDOW(g_wnd_assistant), GTK_FILE_CHOOSER_ACTION_OPEN, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_Open"), GTK_RESPONSE_ACCEPT, NULL ); g_autofree char *filename = NULL; if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); gtk_widget_destroy(dialog); g_autofree char *message = NULL; if (filename) { char *basename = strrchr(filename, '/'); if (!basename) /* wtf? (never happens) */ return; basename++; /* TODO: ask for the name to save it as? For now, just use basename */ struct stat statbuf; if (stat(filename, &statbuf) != 0 || !S_ISREG(statbuf.st_mode)) { message = g_strdup_printf(_("'%s' is not an ordinary file"), filename); goto show_msgbox; } struct problem_item *item = problem_data_get_item_or_NULL(g_cd, basename); if (!item || (item->flags & CD_FLAG_ISEDITABLE)) { struct dump_dir *dd = wizard_open_directory_for_writing(g_dump_dir_name); if (dd) { dd_close(dd); g_autofree char *new_name = g_build_filename(g_dump_dir_name, basename, NULL); if (strcmp(filename, new_name) == 0) message = g_strdup(_("You are trying to copy a file onto itself")); else { off_t r = libreport_copy_file(filename, new_name, 0666); if (r < 0) { message = g_strdup_printf(_("Can't copy '%s': %s"), filename, strerror(errno)); unlink(new_name); } if (!message) { problem_data_reload_from_dump_dir(); update_gui_state_from_problem_data(/* don't update selected event */ 0); /* Set flags for the new item */ update_ls_details_checkboxes(g_event_selected); } } } } else message = g_strdup_printf(_("Item '%s' already exists and is not modifiable"), basename); if (message) { show_msgbox: ; GtkWidget *dlg = gtk_message_dialog_new(GTK_WINDOW(g_wnd_assistant), GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, "%s", message); gtk_window_set_transient_for(GTK_WINDOW(dlg), GTK_WINDOW(g_wnd_assistant)); gtk_dialog_run(GTK_DIALOG(dlg)); gtk_widget_destroy(dlg); } } } static void on_btn_detail(GtkButton *button) { GtkWidget *pdd = problem_details_dialog_new(g_cd, g_wnd_assistant); gtk_dialog_run(GTK_DIALOG(pdd)); } /* [Del] key handling in item list */ static void delete_item(GtkTreeView *treeview) { GtkTreeSelection *selection = gtk_tree_view_get_selection(treeview); if (selection) { GtkTreeIter iter; GtkTreeModel *store = gtk_tree_view_get_model(treeview); if (gtk_tree_selection_get_selected(selection, &store, &iter) == TRUE) { GValue d_item_name = { 0 }; gtk_tree_model_get_value(store, &iter, DETAIL_COLUMN_NAME, &d_item_name); const char *item_name = g_value_get_string(&d_item_name); if (item_name) { struct problem_item *item = problem_data_get_item_or_NULL(g_cd, item_name); if (item->flags & CD_FLAG_ISEDITABLE) { // GtkTreePath *old_path = gtk_tree_model_get_path(store, &iter); struct dump_dir *dd = wizard_open_directory_for_writing(g_dump_dir_name); if (dd) { g_autofree char *filename = g_build_filename(g_dump_dir_name, item_name, NULL); unlink(filename); dd_close(dd); g_hash_table_remove(g_cd, item_name); gtk_list_store_remove(g_ls_details, &iter); } // /* Try to retain the same cursor position */ // sanitize_cursor(old_path); // gtk_tree_path_free(old_path); } } } } } static gint on_key_press_event_in_item_list(GtkTreeView *treeview, GdkEventKey *key, gpointer unused) { int k = key->keyval; if (k == GDK_KEY_Delete || k == GDK_KEY_KP_Delete) { delete_item(treeview); return TRUE; } return FALSE; } static void on_reproducible_changed(GtkComboBox *widget, gpointer user_data) { update_reproducible_hints(); toggle_eb_comment(); } /* Initialization */ static void on_next_btn_cb(GtkWidget *btn, gpointer user_data) { gint current_page_no = gtk_notebook_get_current_page(g_assistant); gint next_page_no = select_next_page_no(current_page_no); /* If page is not changed, 'switch-page' signal is not emitted. */ if (current_page_no == next_page_no) on_page_prepare(g_assistant, gtk_notebook_get_nth_page(g_assistant, next_page_no), NULL); else gtk_notebook_set_current_page(g_assistant, next_page_no); } static void add_pages(void) { g_builder = make_builder(); if (g_provider == NULL) load_css_style(); int i; for (i = 0; page_names[i] != NULL; i++) { GtkWidget *page = GTK_WIDGET(gtk_builder_get_object(g_builder, page_names[i])); pages[i].page_widget = page; gtk_notebook_append_page(g_assistant, page, gtk_label_new(pages[i].title)); log_notice("added page: %s", page_names[i]); } /* Set pointers to objects we might need to work with */ g_lbl_cd_reason = GTK_LABEL( gtk_builder_get_object(g_builder, "lbl_cd_reason")); g_box_events = GTK_BOX( gtk_builder_get_object(g_builder, "vb_events")); g_box_workflows = GTK_BOX( gtk_builder_get_object(g_builder, "vb_workflows")); g_lbl_event_log = GTK_LABEL( gtk_builder_get_object(g_builder, "lbl_event_log")); g_tv_event_log = GTK_TEXT_VIEW( gtk_builder_get_object(g_builder, "tv_event_log")); g_tv_comment = GTK_TEXT_VIEW( gtk_builder_get_object(g_builder, "tv_comment")); g_eb_comment = GTK_EVENT_BOX( gtk_builder_get_object(g_builder, "eb_comment")); g_cb_no_comment = GTK_CHECK_BUTTON( gtk_builder_get_object(g_builder, "cb_no_comment")); g_tv_details = GTK_TREE_VIEW( gtk_builder_get_object(g_builder, "tv_details")); g_tb_approve_bt = GTK_TOGGLE_BUTTON( gtk_builder_get_object(g_builder, "cb_approve_bt")); g_search_entry_bt = GTK_ENTRY( gtk_builder_get_object(g_builder, "entry_search_bt")); g_container_details1 = GTK_CONTAINER( gtk_builder_get_object(g_builder, "container_details1")); g_container_details2 = GTK_CONTAINER( gtk_builder_get_object(g_builder, "container_details2")); g_btn_add_file = GTK_BUTTON( gtk_builder_get_object(g_builder, "btn_add_file")); g_lbl_size = GTK_LABEL( gtk_builder_get_object(g_builder, "lbl_size")); g_notebook = GTK_NOTEBOOK( gtk_builder_get_object(g_builder, "notebook_edit")); g_ls_sensitive_list = GTK_LIST_STORE( gtk_builder_get_object(g_builder, "ls_sensitive_words")); g_tv_sensitive_list = GTK_TREE_VIEW( gtk_builder_get_object(g_builder, "tv_sensitive_words")); g_tv_sensitive_sel = GTK_TREE_SELECTION(gtk_builder_get_object(g_builder, "tv_sensitive_words_selection")); g_rb_forbidden_words = GTK_RADIO_BUTTON( gtk_builder_get_object(g_builder, "rb_forbidden_words")); g_rb_custom_search = GTK_RADIO_BUTTON( gtk_builder_get_object(g_builder, "rb_custom_search")); g_exp_search = GTK_EXPANDER( gtk_builder_get_object(g_builder, "expander_search")); g_report_stack = GTK_WIDGET( gtk_builder_get_object(g_builder, "report-stack")); g_report_status_box = GTK_WIDGET( gtk_builder_get_object(g_builder, "report-status-box")); g_report_warning_box = GTK_WIDGET( gtk_builder_get_object(g_builder, "report-warning-box")); g_spinner_event_log = GTK_SPINNER( gtk_builder_get_object(g_builder, "spinner_event_log")); g_img_process_fail = GTK_IMAGE( gtk_builder_get_object(g_builder, "img_process_fail")); g_report_warning_label_box = GTK_WIDGET( gtk_builder_get_object(g_builder, "report-warning-label-box")); g_exp_report_log = GTK_EXPANDER( gtk_builder_get_object(g_builder, "expand_report")); g_vb_simple_details = GTK_BOX( gtk_builder_get_object(g_builder, "vb_simple_details")); g_cmb_reproducible = GTK_COMBO_BOX_TEXT(gtk_builder_get_object(g_builder, "cmb_reproducible")); g_tv_steps = GTK_TEXT_VIEW( gtk_builder_get_object(g_builder, "tv_steps")); g_vb_complex_details = GTK_BOX( gtk_builder_get_object(g_builder, "vb_complex_details")); g_lbl_complex_details_hint = GTK_LABEL( gtk_builder_get_object(g_builder, "lbl_complex_details_hint")); gtk_widget_set_no_show_all(GTK_WIDGET(g_spinner_event_log), true); gtk_widget_set_name(GTK_WIDGET(g_tv_event_log), "g_tv_event_log"); gtk_widget_set_name(GTK_WIDGET(g_eb_comment), "g_eb_comment"); fix_all_wrapped_labels(GTK_WIDGET(g_assistant)); g_signal_connect(g_cb_no_comment, "toggled", G_CALLBACK(on_no_comment_toggled), NULL); g_signal_connect(g_rb_forbidden_words, "toggled", G_CALLBACK(on_forbidden_words_toggled), NULL); g_signal_connect(g_rb_custom_search, "toggled", G_CALLBACK(on_custom_search_toggled), NULL); g_signal_connect(g_tv_details, "key-press-event", G_CALLBACK(on_key_press_event_in_item_list), NULL); g_tv_sensitive_sel_hndlr = g_signal_connect(g_tv_sensitive_sel, "changed", G_CALLBACK(on_sensitive_word_selection_changed), NULL); gtk_combo_box_text_insert(g_cmb_reproducible, PROBLEM_REPRODUCIBLE_UNKNOWN, NULL, _("I have experienced this problem for the first time")); gtk_combo_box_text_insert(g_cmb_reproducible, PROBLEM_REPRODUCIBLE_YES, NULL, _("I can reproduce this problem")); gtk_combo_box_text_insert(g_cmb_reproducible, PROBLEM_REPRODUCIBLE_RECURRENT, NULL, _("This problem occurs repeatedly")); g_signal_connect(g_cmb_reproducible, "changed", G_CALLBACK(on_reproducible_changed), NULL); } static void create_details_treeview(void) { GtkCellRenderer *renderer; GtkTreeViewColumn *column; renderer = gtk_cell_renderer_toggle_new(); column = gtk_tree_view_column_new_with_attributes( _("Include"), renderer, /* which "attr" of renderer to set from which COLUMN? (can be repeated) */ "active", DETAIL_COLUMN_CHECKBOX, NULL); g_tv_details_col_checkbox = column; gtk_tree_view_append_column(g_tv_details, column); /* This column has a handler */ g_signal_connect(renderer, "toggled", G_CALLBACK(g_tv_details_checkbox_toggled), NULL); renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( _("Name"), renderer, "text", DETAIL_COLUMN_NAME, NULL); gtk_tree_view_append_column(g_tv_details, column); /* This column has a clickable header for sorting */ gtk_tree_view_column_set_sort_column_id(column, DETAIL_COLUMN_NAME); g_tv_details_renderer_value = renderer = gtk_cell_renderer_text_new(); g_signal_connect(renderer, "edited", G_CALLBACK(save_edited_one_liner), NULL); column = gtk_tree_view_column_new_with_attributes( _("Value"), renderer, "text", DETAIL_COLUMN_VALUE, NULL); gtk_tree_view_append_column(g_tv_details, column); /* This column has a clickable header for sorting */ gtk_tree_view_column_set_sort_column_id(column, DETAIL_COLUMN_VALUE); /* renderer = gtk_cell_renderer_text_new(); column = gtk_tree_view_column_new_with_attributes( _("Path"), renderer, "text", DETAIL_COLUMN_PATH, NULL); gtk_tree_view_append_column(g_tv_details, column); */ g_ls_details = gtk_list_store_new(DETAIL_NUM_COLUMNS, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING); gtk_tree_view_set_model(g_tv_details, GTK_TREE_MODEL(g_ls_details)); g_signal_connect(g_tv_details, "row-activated", G_CALLBACK(tv_details_row_activated), NULL); g_signal_connect(g_tv_details, "cursor-changed", G_CALLBACK(tv_details_cursor_changed), NULL); g_signal_connect(g_tv_details, "map", G_CALLBACK(tv_details_show), NULL); /* [Enter] on a row: * g_signal_connect(g_tv_details, "select-cursor-row", G_CALLBACK(tv_details_select_cursor_row), NULL); */ } static void init_page(page_obj_t *page, const char *name, const char *title) { page->name = name; page->title = title; } static void init_pages(void) { init_page(&pages[0], PAGE_SUMMARY , _("Problem description") ); init_page(&pages[1], PAGE_EVENT_SELECTOR , _("Select how to report this problem")); init_page(&pages[2], PAGE_EDIT_COMMENT , _("Provide additional information")); init_page(&pages[3], PAGE_EDIT_ELEMENTS , _("Review the data") ); init_page(&pages[4], PAGE_REVIEW_DATA , _("Confirm data to report")); init_page(&pages[5], PAGE_EVENT_PROGRESS , _("Processing") ); init_page(&pages[6], PAGE_EVENT_DONE , _("Processing done") ); //do we still need this? init_page(&pages[7], PAGE_NOT_SHOWN , "" ); } static void assistant_quit_cb(void *obj, void *data) { /* Suppress execution of consume_cmd_output() */ if (g_event_source_id != 0) { g_source_remove(g_event_source_id); g_event_source_id = 0; } cancel_event_run(); if (g_loaded_texts) { g_hash_table_destroy(g_loaded_texts); g_loaded_texts = NULL; } gtk_widget_destroy(GTK_WIDGET(g_wnd_assistant)); g_wnd_assistant = (void *)0xdeadbeaf; } void create_assistant(GtkApplication *app) { g_loaded_texts = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); g_assistant = GTK_NOTEBOOK(gtk_notebook_new()); /* Since someone thought it would be a bright idea to use a GtkNotebook * as GtkAssistant, we need to help the user not shoot themselves in the * face^Wfoot. Navigating freely will likely reset some state or flat out crash. */ gtk_notebook_set_show_tabs(g_assistant, FALSE); g_btn_close = gtk_button_new_with_mnemonic(_("_Close")); gtk_button_set_image(GTK_BUTTON(g_btn_close), gtk_image_new_from_icon_name("window-close-symbolic", GTK_ICON_SIZE_BUTTON)); g_btn_stop = gtk_button_new_with_mnemonic(_("_Stop")); gtk_button_set_image(GTK_BUTTON(g_btn_stop), gtk_image_new_from_icon_name("process-stop-symbolic", GTK_ICON_SIZE_BUTTON)); gtk_widget_set_no_show_all(g_btn_stop, true); /* else gtk_widget_hide won't work */ g_btn_repeat = gtk_button_new_with_label(_("Repeat")); gtk_widget_set_no_show_all(g_btn_repeat, true); /* else gtk_widget_hide won't work */ g_btn_next = gtk_button_new_with_mnemonic(_("_Forward")); gtk_button_set_image(GTK_BUTTON(g_btn_next), gtk_image_new_from_icon_name("go-next-symbolic", GTK_ICON_SIZE_BUTTON)); gtk_widget_set_no_show_all(g_btn_next, true); /* else gtk_widget_hide won't work */ g_btn_detail = gtk_button_new_with_mnemonic(_("Details")); gtk_widget_set_no_show_all(g_btn_detail, true); /* else gtk_widget_hide won't work */ g_box_buttons = GTK_BOX(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); gtk_box_pack_start(g_box_buttons, g_btn_close, false, false, 5); gtk_box_pack_start(g_box_buttons, g_btn_stop, false, false, 5); gtk_box_pack_start(g_box_buttons, g_btn_repeat, false, false, 5); /* Btns above are to the left, the rest are to the right: */ gtk_widget_set_valign(GTK_WIDGET(g_btn_next), GTK_ALIGN_END); gtk_box_pack_end(g_box_buttons, g_btn_next, false, false, 5); gtk_box_pack_end(g_box_buttons, g_btn_detail, false, false, 5); { /* Warnings area widget definition start */ g_box_warning_labels = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0)); gtk_widget_set_visible(GTK_WIDGET(g_box_warning_labels), TRUE); GtkBox *vbox = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0)); gtk_widget_set_visible(GTK_WIDGET(vbox), TRUE); gtk_box_pack_start(vbox, GTK_WIDGET(g_box_warning_labels), false, false, 5); GtkWidget *image = gtk_image_new_from_icon_name("dialog-warning-symbolic", GTK_ICON_SIZE_DIALOG); gtk_widget_set_visible(image, TRUE); g_widget_warnings_area = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); gtk_widget_set_visible(g_widget_warnings_area, FALSE); gtk_widget_set_no_show_all(g_widget_warnings_area, TRUE); gtk_widget_set_valign(GTK_WIDGET(image), GTK_ALIGN_CENTER); gtk_widget_set_valign(GTK_WIDGET(vbox), GTK_ALIGN_CENTER); gtk_box_pack_start(GTK_BOX(g_widget_warnings_area), image, false, false, 5); gtk_box_pack_start(GTK_BOX(g_widget_warnings_area), GTK_WIDGET(vbox), false, false, 0); } /* Warnings area widget definition end */ g_box_assistant = GTK_BOX(gtk_box_new(GTK_ORIENTATION_VERTICAL, 0)); gtk_box_pack_start(g_box_assistant, GTK_WIDGET(g_assistant), true, true, 0); gtk_box_pack_start(g_box_assistant, GTK_WIDGET(g_widget_warnings_area), false, false, 0); /* Private ticket warning */ { g_sens_ticket = GTK_WIDGET(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0)); gtk_widget_set_no_show_all(GTK_WIDGET(g_sens_ticket), TRUE); gtk_widget_hide(GTK_WIDGET(g_sens_ticket)); g_sens_ticket_cb = GTK_TOGGLE_BUTTON(gtk_check_button_new_with_label(_("Restrict access to the report"))); gtk_widget_set_margin_start(GTK_WIDGET(g_sens_ticket_cb), 5); gtk_widget_show(GTK_WIDGET(g_sens_ticket_cb)); g_signal_connect(g_sens_ticket_cb, "toggled", G_CALLBACK(on_sensitive_ticket_clicked_cb), NULL); GtkLinkButton *privacy_info_btn = GTK_LINK_BUTTON(gtk_link_button_new_with_label("", _("Learn more about restricted access in the configuration"))); gtk_widget_show(GTK_WIDGET(privacy_info_btn)); g_signal_connect(privacy_info_btn, "clicked", G_CALLBACK(on_privacy_info_btn), NULL); gtk_box_pack_start(GTK_BOX(g_sens_ticket), GTK_WIDGET(g_sens_ticket_cb), false, false, 5); gtk_box_pack_start(GTK_BOX(g_sens_ticket), GTK_WIDGET(privacy_info_btn), false, false, 5); gtk_box_pack_start(g_box_assistant, GTK_WIDGET(g_sens_ticket), false, true, 5); } gtk_box_pack_start(g_box_assistant, GTK_WIDGET(g_box_buttons), false, false, 5); gtk_widget_show_all(GTK_WIDGET(g_box_buttons)); gtk_widget_hide(g_btn_stop); gtk_widget_hide(g_btn_repeat); gtk_widget_show(g_btn_next); g_wnd_assistant = GTK_WINDOW(gtk_application_window_new(app)); gtk_container_add(GTK_CONTAINER(g_wnd_assistant), GTK_WIDGET(g_box_assistant)); gtk_window_set_default_size(g_wnd_assistant, DEFAULT_WIDTH, DEFAULT_HEIGHT); /* set_default sets icon for every windows used in this app, so we don't * have to set the icon for those windows manually */ gtk_window_set_default_icon_name("abrt"); init_pages(); add_pages(); create_details_treeview(); ProblemDetailsWidget *details = problem_details_widget_new(g_cd); gtk_container_add(GTK_CONTAINER(g_container_details1), GTK_WIDGET(details)); g_signal_connect(g_btn_close, "clicked", G_CALLBACK(assistant_quit_cb), NULL); g_signal_connect(g_btn_stop, "clicked", G_CALLBACK(on_btn_cancel_event), NULL); g_signal_connect(g_btn_repeat, "clicked", G_CALLBACK(on_btn_repeat_cb), NULL); g_signal_connect(g_btn_next, "clicked", G_CALLBACK(on_next_btn_cb), NULL); g_signal_connect(g_wnd_assistant, "destroy", G_CALLBACK(assistant_quit_cb), NULL); g_signal_connect(g_assistant, "switch-page", G_CALLBACK(on_page_prepare), NULL); g_signal_connect(g_tb_approve_bt, "toggled", G_CALLBACK(on_bt_approve_toggle), NULL); g_signal_connect(gtk_text_view_get_buffer(g_tv_comment), "changed", G_CALLBACK(on_comment_changed), NULL); g_signal_connect(gtk_text_view_get_buffer(g_tv_steps), "changed", G_CALLBACK(on_steps_changed), NULL); g_signal_connect(g_btn_add_file, "clicked", G_CALLBACK(on_btn_add_file), NULL); g_signal_connect(g_btn_detail, "clicked", G_CALLBACK(on_btn_detail), NULL); g_signal_connect(g_search_entry_bt, "changed", G_CALLBACK(search_timeout), NULL); g_signal_connect(g_tv_event_log, "key-press-event", G_CALLBACK(key_press_event), NULL); g_signal_connect(g_tv_event_log, "event-after", G_CALLBACK(event_after), NULL); g_signal_connect(g_tv_event_log, "motion-notify-event", G_CALLBACK(motion_notify_event), NULL); g_signal_connect(g_tv_event_log, "visibility-notify-event", G_CALLBACK(visibility_notify_event), NULL); g_signal_connect(gtk_text_view_get_buffer(g_tv_event_log), "changed", G_CALLBACK(on_log_changed), NULL); hand_cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_HAND2); regular_cursor = gdk_cursor_new_for_display(gdk_display_get_default(), GDK_XTERM); /* Switch right to starting page. */ on_page_prepare(g_assistant, gtk_notebook_get_nth_page(g_assistant, 0), NULL); }