/*
* 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 <config.h>
#endif
#include "abrt-config-widget.h"
#include <satyr/utils.h>
#include <gio/gdesktopappinfo.h>
#include "libabrt.h"
#include <assert.h>
#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("<i>%s</i>\n\n<a href=\"%s\">Privacy Policy</a>", message, privacy_policy);
else
markup = xasprintf("<i>%s</i>", 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("<i>%s</i>", 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);
}
}