/* * Copyright (C) 2012 Red Hat * * 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 St, Fifth Floor, Boston, MA 02110-1301 USA */ #ifdef HAVE_CONFIG_H #include #endif #include "abrt-config-widget.h" #include #include #include "libabrt.h" #include #define ABRT_CONFIG_WIDGET_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE((o), TYPE_ABRT_CONFIG_WIDGET, AbrtConfigWidgetPrivate)) #define WID(s) GTK_WIDGET(gtk_builder_get_object(self->priv->builder, s)) #define UI_FILE_NAME "abrt-config-widget.glade" /* AbrtConfigWidgetPrivate: * + AbrtConfigWidgetOption == "abrt-option" of GtkSwitch * + AbrtConfigWidgetOption == "abrt-option" of GtkSwitch * + ... * * + AbrtAppConfiguration == config of AbrtConfigWidgetOption * + AbrtAppConfiguration == config of AbrtConfigWidgetOption * + ... */ /* This structure represents either an ABRT configuration file or a GSettings * schema. */ typedef struct { char *app_name; ///< e.g abrt-applet, org.gnome.desktop.privacy map_string_t *settings; ///< ABRT configuration file GSettings *glib_settings; ///< GSettings } AbrtAppConfiguration; /* This structure represents a single switch. */ typedef struct { const char *name; ///< e.g. ask_steal_dir, report-technical-problems GtkSwitch *switch_widget; GtkWidget *radio_button_widget[3]; int default_value; int current_value; AbrtAppConfiguration *config; } AbrtConfigWidgetOption; /* Each configuration option has its own number. */ enum AbrtOptions { _ABRT_OPT_BEGIN_, _ABRT_OPT_SWITCH_BEGIN_= _ABRT_OPT_BEGIN_, ABRT_OPT_STEAL_DIRECTORY= _ABRT_OPT_BEGIN_, ABRT_OPT_PRIVATE_TICKET, ABRT_OPT_SEND_UREPORT, ABRT_OPT_SHORTENED_REPORTING, ABRT_OPT_SILENT_SHORTENED_REPORTING, ABRT_OPT_NOTIFY_INCOMPLETE_PROBLEMS, _ABRT_OPT_SWITCH_END_, _ABRT_RADIOBUTTON_OPT_BEGIN_= _ABRT_OPT_SWITCH_END_, ABRT_OPT_UPLOAD_COREDUMP= _ABRT_OPT_SWITCH_END_, _ABRT_OPT_END_, }; enum AbrtRadioButtonOptions { _ABRT_RADIOBUTTON_OPT_ = -1, ABRT_RADIOBUTTON_OPT_NEVER = 0, ABRT_RADIOBUTTON_OPT_ALWAYS = 1, ABRT_RADIOBUTTON_OPT_ASK = 2, }; /* This structure holds private data of AbrtConfigWidget */ struct AbrtConfigWidgetPrivate { GtkBuilder *builder; AbrtAppConfiguration *report_gtk_conf; AbrtAppConfiguration *abrt_applet_conf; AbrtAppConfiguration *privacy_gsettings; /* Static array for all switches */ AbrtConfigWidgetOption options[_ABRT_OPT_END_]; }; G_DEFINE_TYPE(AbrtConfigWidget, abrt_config_widget, GTK_TYPE_BOX) enum { SN_CHANGED, SN_LAST_SIGNAL } SignalNumber; static guint s_signals[SN_LAST_SIGNAL] = { 0 }; static void abrt_config_widget_finalize(GObject *object); /* New ABRT configuration file wrapper */ static AbrtAppConfiguration * abrt_app_configuration_new(const char *app_name) { AbrtAppConfiguration *conf = xmalloc(sizeof(*conf)); conf->app_name = xstrdup(app_name); conf->settings = new_map_string(); conf->glib_settings = NULL; if(!load_app_conf_file(conf->app_name, conf->settings)) { g_warning("Failed to load config for '%s'", conf->app_name); } return conf; } /* New GSettings wrapper */ static AbrtAppConfiguration * abrt_app_configuration_new_glib(const char *schema) { AbrtAppConfiguration *conf = xmalloc(sizeof(*conf)); conf->app_name = xstrdup(schema); conf->settings = NULL; conf->glib_settings = g_settings_new(conf->app_name); return conf; } static void abrt_app_configuration_set_value(AbrtAppConfiguration *conf, const char *name, const char *value) { if (conf->settings) set_app_user_setting(conf->settings, name, value); else if (conf->glib_settings) g_settings_set_boolean(conf->glib_settings, name, string_to_bool(value)); else assert(!"BUG: not properly initialized AbrtAppConfiguration"); } static const char * abrt_app_configuration_get_value(AbrtAppConfiguration *conf, const char *name) { if (conf->settings) { const char *val = get_app_user_setting(conf->settings, name); return (val == NULL || strcmp(val, "") == 0) ? NULL : val; } if (conf->glib_settings) return g_settings_get_boolean(conf->glib_settings, name) ? "yes" : "no"; assert(!"BUG: not properly initialized AbrtAppConfiguration"); } static void abrt_app_configuration_save(AbrtAppConfiguration *conf) { if (conf->settings) save_app_conf_file(conf->app_name, conf->settings); /* No need to save GSettings because changes are applied instantly */ } static void abrt_app_configuration_free(AbrtAppConfiguration *conf) { if (!conf) return; free(conf->app_name); conf->app_name = (void *)0xDEADBEAF; if (conf->settings) { free_map_string(conf->settings); conf->settings = (void *)0xDEADBEAF; } if (conf->glib_settings) { g_object_unref(conf->glib_settings); conf->glib_settings = (void *)0xDEADBEAF; } } static void abrt_config_widget_class_init(AbrtConfigWidgetClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = abrt_config_widget_finalize; g_type_class_add_private(klass, sizeof(AbrtConfigWidgetPrivate)); s_signals[SN_CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET(AbrtConfigWidgetClass, changed), /*accumulator*/NULL, /*accu_data*/NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, /*n_params*/0); } static void abrt_config_widget_finalize(GObject *object) { AbrtConfigWidget *self; self = ABRT_CONFIG_WIDGET(object); if(self->priv->builder) { g_object_unref(self->priv->builder); self->priv->builder = NULL; } /* Clean up */ abrt_app_configuration_free(self->priv->report_gtk_conf); self->priv->report_gtk_conf = NULL; abrt_app_configuration_free(self->priv->abrt_applet_conf); self->priv->abrt_applet_conf = NULL; abrt_app_configuration_free(self->priv->privacy_gsettings); self->priv->privacy_gsettings = NULL; G_OBJECT_CLASS(abrt_config_widget_parent_class)->finalize(object); } static void emit_change(AbrtConfigWidget *config) { g_signal_emit(config, s_signals[SN_CHANGED], 0); } static void on_switch_activate(GObject *object, GParamSpec *spec, AbrtConfigWidget *config) { AbrtConfigWidgetOption *option = g_object_get_data(G_OBJECT(object), "abrt-option"); if (option->config == NULL) return; const gboolean state = gtk_switch_get_active(GTK_SWITCH(object)); const char *const val = state ? "yes" : "no"; log_debug("%s : %s", option->name, val); abrt_app_configuration_set_value(option->config, option->name, val); abrt_app_configuration_save(option->config); emit_change(config); } static void on_radio_button_toggle(GObject *object, AbrtConfigWidget *config) { /* inactive radio button */ if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(object)) == false) return; AbrtConfigWidgetOption *option = g_object_get_data(G_OBJECT(object), "abrt-option"); if (option->config == NULL) return; /* get active radio button */ const char *val = g_object_get_data(G_OBJECT(object), "abrt-triple-switch-value"); log_debug("%s : %s", option->name, val); abrt_app_configuration_set_value(option->config, option->name, val); abrt_app_configuration_save(option->config); emit_change(config); } static void update_option_switch_current_value(AbrtConfigWidget *self, enum AbrtOptions opid) { assert((opid >= _ABRT_OPT_SWITCH_BEGIN_ && opid < _ABRT_OPT_SWITCH_END_) || !"Out of range Option ID value"); AbrtConfigWidgetOption *option = &(self->priv->options[opid]); const char *val = NULL; if (option->config != NULL) val = abrt_app_configuration_get_value(option->config, option->name); option->current_value = val ? string_to_bool(val) : option->default_value; } static void update_option_radio_button_current_value(AbrtConfigWidget *self, enum AbrtOptions opid) { assert((opid >= _ABRT_RADIOBUTTON_OPT_BEGIN_ && opid < _ABRT_OPT_END_) || !"Out of range Option ID value"); AbrtConfigWidgetOption *option = &(self->priv->options[opid]); const char *val = NULL; if (option->config != NULL) val = abrt_app_configuration_get_value(option->config, option->name); if (val == NULL) option->current_value = option->default_value; else if (string_to_bool(val)) option->current_value = ABRT_RADIOBUTTON_OPT_ALWAYS; else option->current_value = ABRT_RADIOBUTTON_OPT_NEVER; } static void connect_switch_with_option(AbrtConfigWidget *self, enum AbrtOptions opid, const char *switch_name) { assert((opid >= _ABRT_OPT_SWITCH_BEGIN_ && opid < _ABRT_OPT_SWITCH_END_) || !"Out of range Option ID value"); AbrtConfigWidgetOption *option = &(self->priv->options[opid]); update_option_switch_current_value(self, opid); GtkSwitch *gsw = GTK_SWITCH(WID(switch_name)); option->switch_widget = gsw; gtk_switch_set_active(gsw, (gboolean)option->current_value); g_object_set_data(G_OBJECT(gsw), "abrt-option", option); g_signal_connect(G_OBJECT(gsw), "notify::active", G_CALLBACK(on_switch_activate), self); /* If the option has no config, make the corresponding insensitive. */ gtk_widget_set_sensitive(GTK_WIDGET(gsw), option->config != NULL); } static void connect_radio_buttons_with_option(AbrtConfigWidget *self, enum AbrtOptions opid, const char *btn_always_name, const char *btn_never_name, const char *btn_ask_name) { assert((opid >= _ABRT_RADIOBUTTON_OPT_BEGIN_ && opid < _ABRT_OPT_END_) || !"Out of range Option ID value"); AbrtConfigWidgetOption *option = &(self->priv->options[opid]); update_option_radio_button_current_value(self, opid); GtkWidget *btn_always = WID(btn_always_name); GtkWidget *btn_never = WID(btn_never_name); GtkWidget *btn_ask = WID(btn_ask_name); option->radio_button_widget[ABRT_RADIOBUTTON_OPT_ALWAYS] = btn_always; option->radio_button_widget[ABRT_RADIOBUTTON_OPT_NEVER] = btn_never; option->radio_button_widget[ABRT_RADIOBUTTON_OPT_ASK] = btn_ask; GtkWidget *active_button = option->radio_button_widget[option->current_value]; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(active_button), TRUE); g_object_set_data(G_OBJECT(btn_always), "abrt-option", option); g_object_set_data(G_OBJECT(btn_always), "abrt-triple-switch-value", (char *)"yes"); g_object_set_data(G_OBJECT(btn_never), "abrt-option", option); g_object_set_data(G_OBJECT(btn_never), "abrt-triple-switch-value", (char *)"no"); g_object_set_data(G_OBJECT(btn_ask), "abrt-option", option); g_object_set_data(G_OBJECT(btn_ask), "abrt-triple-switch-value", NULL); g_signal_connect(btn_always, "toggled", G_CALLBACK(on_radio_button_toggle), self); g_signal_connect(btn_never, "toggled", G_CALLBACK(on_radio_button_toggle), self); g_signal_connect(btn_ask, "toggled", G_CALLBACK(on_radio_button_toggle), self); /* If the option has no config, make the corresponding insensitive. */ gtk_widget_set_sensitive(GTK_WIDGET(btn_always), option->config != NULL); gtk_widget_set_sensitive(GTK_WIDGET(btn_never), option->config != NULL); gtk_widget_set_sensitive(GTK_WIDGET(btn_ask), option->config != NULL); } static void pp_launcher_clicked(GtkButton *launcher, gpointer *unused_data) { GDesktopAppInfo *app = g_object_get_data(G_OBJECT(launcher), "launched-app"); GError *err = NULL; if (!g_app_info_launch(G_APP_INFO(app), NULL, NULL, &err)) { perror_msg("Could not launch '%s': %s", g_desktop_app_info_get_filename(G_DESKTOP_APP_INFO (app)), err->message); } } static void os_release_callback(char *key, char *value, void *data) { if (strcmp(key, "PRIVACY_POLICY") == 0) *(char **)data = value; else free(value); free(key); } static void abrt_config_widget_init(AbrtConfigWidget *self) { GError *error = NULL; self->priv = ABRT_CONFIG_WIDGET_GET_PRIVATE(self); self->priv->builder = gtk_builder_new(); gtk_builder_set_translation_domain(self->priv->builder, GETTEXT_PACKAGE); gtk_builder_add_from_file(self->priv->builder, UI_FILE_NAME, &error); if(error != NULL) { log_debug("Failed to load '%s': %s", UI_FILE_NAME, error->message); g_error_free(error); error = NULL; gtk_builder_add_from_file(self->priv->builder, ABRT_UI_DIR "/" UI_FILE_NAME, &error); if(error != NULL) { g_warning("Failed to load '%s': %s", ABRT_UI_DIR "/" UI_FILE_NAME, error->message); g_error_free(error); return; } } /* Load configuration */ load_abrt_conf(); self->priv->report_gtk_conf = abrt_app_configuration_new("report-gtk"); self->priv->abrt_applet_conf = abrt_app_configuration_new("abrt-applet"); self->priv->privacy_gsettings = abrt_app_configuration_new_glib("org.gnome.desktop.privacy"); /* Initialize options */ /* report-gtk */ self->priv->options[ABRT_OPT_STEAL_DIRECTORY].name = "ask_steal_dir"; self->priv->options[ABRT_OPT_STEAL_DIRECTORY].default_value = TRUE; self->priv->options[ABRT_OPT_STEAL_DIRECTORY].config = self->priv->report_gtk_conf; self->priv->options[ABRT_OPT_UPLOAD_COREDUMP].name = "abrt_analyze_upload_coredump"; self->priv->options[ABRT_OPT_UPLOAD_COREDUMP].default_value = ABRT_RADIOBUTTON_OPT_ASK; self->priv->options[ABRT_OPT_UPLOAD_COREDUMP].config = self->priv->report_gtk_conf; self->priv->options[ABRT_OPT_PRIVATE_TICKET].name = CREATE_PRIVATE_TICKET; self->priv->options[ABRT_OPT_PRIVATE_TICKET].default_value = FALSE; self->priv->options[ABRT_OPT_PRIVATE_TICKET].config = self->priv->report_gtk_conf; /* abrt-applet */ self->priv->options[ABRT_OPT_SEND_UREPORT].name = "report-technical-problems"; self->priv->options[ABRT_OPT_SEND_UREPORT].default_value = string_to_bool(abrt_app_configuration_get_value(self->priv->privacy_gsettings, "report-technical-problems")); { /* Get the container widget for the lauch button and warnings */ GtkWidget *hbox_auto_reporting = WID("hbox_auto_reporting"); assert(hbox_auto_reporting); /* Be able to use another desktop file while debugging */ const char *gpp_app = getenv("ABRT_PRIVACY_APP_DESKTOP"); if (gpp_app == NULL) gpp_app = "gnome-privacy-panel.desktop"; GDesktopAppInfo *app = g_desktop_app_info_new(gpp_app); char *message = NULL; char *markup = NULL; if (!app) { /* Make the switch editable */ self->priv->options[ABRT_OPT_SEND_UREPORT].config = self->priv->privacy_gsettings; char *os_release = xmalloc_open_read_close("/etc/os-release", /*no size limit*/NULL); char *privacy_policy = NULL; /* Try to get the value of PRIVACY_POLICY from /etc/os-release */ sr_parse_os_release(os_release, os_release_callback, (void *)&privacy_policy); message = xasprintf(_("The configuration option above has been moved to GSettings and " "the switch is linked to the value of the setting 'report-technical-problems' " "from the schema 'org.gnome.desktop.privacy'.")); /* Do not add Privacy Policy link if /etc/os-release does not contain PRIVACY_POLICY */ if (privacy_policy != NULL) markup = xasprintf("%s\n\nPrivacy Policy", message, privacy_policy); else markup = xasprintf("%s", message); free(privacy_policy); free(os_release); } else { /* Make the switch read-only */ self->priv->options[ABRT_OPT_SEND_UREPORT].config = NULL; message = xasprintf(_("The configuration option above can be configured in")); markup = xasprintf("%s", message); GtkWidget *launcher = gtk_button_new_with_label(g_app_info_get_display_name(G_APP_INFO(app))); /* Here we could pass the launcher to pp_launcher_clicked() as the * 4th argument of g_signal_connect() but we would leek the * launcher's memory. Therefore we need to find a way how to free * the launcher when it is not needed anymore. GtkWidget inherits * from GObject which offers a functionality for attaching an * arbitrary data to its instances. The last argument is a function * called to destroy the arbirarty data when the instance is being * destroyed. */ g_object_set_data_full(G_OBJECT(launcher), "launched-app", app, g_object_unref); g_signal_connect(launcher, "clicked", G_CALLBACK(pp_launcher_clicked), NULL); /* Make the launcher button narrow, otherwise it would expand to * the width of the warninig. */ gtk_widget_set_hexpand(launcher, FALSE); gtk_widget_set_vexpand(launcher, FALSE); /* Make the launcher button aligned on center of the warning. */ gtk_widget_set_halign(launcher, GTK_ALIGN_CENTER); gtk_widget_set_valign(launcher, GTK_ALIGN_CENTER); gtk_box_pack_end(GTK_BOX(hbox_auto_reporting), launcher, false, false, 0); } GtkWidget *lbl = gtk_label_new(message); gtk_label_set_markup(GTK_LABEL(lbl), markup); /* Do not expand the window by too long warning. */ gtk_label_set_line_wrap(GTK_LABEL(lbl), TRUE); /* Let users to copy the warning. */ gtk_label_set_selectable(GTK_LABEL(lbl), TRUE); free(markup); free(message); gtk_box_pack_start(GTK_BOX(hbox_auto_reporting), lbl, false, false, 0); } self->priv->options[ABRT_OPT_SHORTENED_REPORTING].name = "ShortenedReporting"; self->priv->options[ABRT_OPT_SHORTENED_REPORTING].default_value = g_settings_shortenedreporting; self->priv->options[ABRT_OPT_SHORTENED_REPORTING].config = self->priv->abrt_applet_conf; self->priv->options[ABRT_OPT_SILENT_SHORTENED_REPORTING].name = "SilentShortenedReporting"; self->priv->options[ABRT_OPT_SILENT_SHORTENED_REPORTING].default_value = FALSE; self->priv->options[ABRT_OPT_SILENT_SHORTENED_REPORTING].config = self->priv->abrt_applet_conf; self->priv->options[ABRT_OPT_NOTIFY_INCOMPLETE_PROBLEMS].name = "NotifyIncompleteProblems"; self->priv->options[ABRT_OPT_NOTIFY_INCOMPLETE_PROBLEMS].default_value = FALSE; self->priv->options[ABRT_OPT_NOTIFY_INCOMPLETE_PROBLEMS].config = self->priv->abrt_applet_conf; /* Connect radio buttons with options */ connect_radio_buttons_with_option(self, ABRT_OPT_UPLOAD_COREDUMP, "bg_always", "bg_never", "bg_ask" ); /* Connect widgets with options */ connect_switch_with_option(self, ABRT_OPT_STEAL_DIRECTORY, "switch_steal_directory"); connect_switch_with_option(self, ABRT_OPT_PRIVATE_TICKET, "switch_private_ticket"); connect_switch_with_option(self, ABRT_OPT_SEND_UREPORT, "switch_send_ureport"); connect_switch_with_option(self, ABRT_OPT_SHORTENED_REPORTING, "switch_shortened_reporting"); connect_switch_with_option(self, ABRT_OPT_SILENT_SHORTENED_REPORTING, "switch_silent_shortened_reporting"); connect_switch_with_option(self, ABRT_OPT_NOTIFY_INCOMPLETE_PROBLEMS, "switch_notify_incomplete_problems"); #if ((GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION < 13) || (GTK_MAJOR_VERSION == 3 && GTK_MINOR_VERSION == 13 && GTK_MICRO_VERSION == 1)) /* https://developer.gnome.org/gtk3/3.13/GtkWidget.html#gtk-widget-reparent */ /* gtk_widget_reparent has been deprecated since version 3.13.2 and should not be used in newly-written code. */ gtk_widget_reparent(WID("grid"), GTK_WIDGET(self)); #else gtk_container_remove(GTK_CONTAINER(WID("window1")), WID("grid")); gtk_container_add(GTK_CONTAINER(self), WID("grid")); #endif /* Set the initial state of the properties */ gtk_widget_show_all(GTK_WIDGET(self)); } AbrtConfigWidget * abrt_config_widget_new() { return g_object_new(TYPE_ABRT_CONFIG_WIDGET, NULL); } void abrt_config_widget_reset_to_defaults(AbrtConfigWidget *self) { for(unsigned i = _ABRT_OPT_SWITCH_BEGIN_; i < _ABRT_OPT_SWITCH_END_; ++i) gtk_switch_set_active(self->priv->options[i].switch_widget, self->priv->options[i].default_value); for(unsigned i = _ABRT_RADIOBUTTON_OPT_BEGIN_; i < _ABRT_OPT_END_; ++i) { unsigned default_value = self->priv->options[i].default_value; GtkWidget *radio_button = self->priv->options[i].radio_button_widget[default_value]; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(radio_button), TRUE); } }