diff --git a/libmalcontent-ui/meson.build b/libmalcontent-ui/meson.build index 06208a3..31241ed 100644 --- a/libmalcontent-ui/meson.build +++ b/libmalcontent-ui/meson.build @@ -70,14 +70,14 @@ pkgconfig.generate(libmalcontent_ui, libraries_private: libmalcontent_ui_private_deps, ) -gnome.generate_gir(libmalcontent_ui, +libmalcontent_ui_gir = gnome.generate_gir(libmalcontent_ui, sources: libmalcontent_ui_sources + libmalcontent_ui_headers + libmalcontent_ui_private_headers, nsversion: libmalcontent_ui_api_version, namespace: 'MalcontentUi', symbol_prefix: 'mct_', identifier_prefix: 'Mct', export_packages: 'libmalcontent-ui', - includes: ['AccountsService-1.0', 'Gio-2.0', 'GObject-2.0', 'Gtk-3.0'], + includes: ['AccountsService-1.0', 'Gio-2.0', 'GObject-2.0', 'Gtk-3.0', libmalcontent_gir[0]], install: true, dependencies: libmalcontent_ui_dep, ) diff --git a/libmalcontent-ui/restrict-applications-dialog.c b/libmalcontent-ui/restrict-applications-dialog.c index 98f663a..d231466 100644 --- a/libmalcontent-ui/restrict-applications-dialog.c +++ b/libmalcontent-ui/restrict-applications-dialog.c @@ -19,7 +19,6 @@ * - Philip Withnall */ -#include #include #include #include @@ -55,7 +54,7 @@ struct _MctRestrictApplicationsDialog GtkLabel *description; MctAppFilter *app_filter; /* (owned) (not nullable) */ - ActUser *user; /* (owned) (nullable) */ + gchar *user_display_name; /* (owned) (nullable) */ }; G_DEFINE_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, GTK_TYPE_DIALOG) @@ -63,10 +62,10 @@ G_DEFINE_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, typedef enum { PROP_APP_FILTER = 1, - PROP_USER, + PROP_USER_DISPLAY_NAME, } MctRestrictApplicationsDialogProperty; -static GParamSpec *properties[PROP_USER + 1]; +static GParamSpec *properties[PROP_USER_DISPLAY_NAME + 1]; static void mct_restrict_applications_dialog_constructed (GObject *obj) @@ -74,7 +73,9 @@ mct_restrict_applications_dialog_constructed (GObject *obj) MctRestrictApplicationsDialog *self = MCT_RESTRICT_APPLICATIONS_DIALOG (obj); g_assert (self->app_filter != NULL); - g_assert (self->user == NULL || ACT_IS_USER (self->user)); + g_assert (self->user_display_name == NULL || + (*self->user_display_name != '\0' && + g_utf8_validate (self->user_display_name, -1, NULL))); G_OBJECT_CLASS (mct_restrict_applications_dialog_parent_class)->constructed (obj); } @@ -93,8 +94,8 @@ mct_restrict_applications_dialog_get_property (GObject *object, g_value_set_boxed (value, self->app_filter); break; - case PROP_USER: - g_value_set_object (value, self->user); + case PROP_USER_DISPLAY_NAME: + g_value_set_string (value, self->user_display_name); break; default: @@ -116,8 +117,8 @@ mct_restrict_applications_dialog_set_property (GObject *object, mct_restrict_applications_dialog_set_app_filter (self, g_value_get_boxed (value)); break; - case PROP_USER: - mct_restrict_applications_dialog_set_user (self, g_value_get_object (value)); + case PROP_USER_DISPLAY_NAME: + mct_restrict_applications_dialog_set_user_display_name (self, g_value_get_string (value)); break; default: @@ -131,7 +132,7 @@ mct_restrict_applications_dialog_dispose (GObject *object) MctRestrictApplicationsDialog *self = (MctRestrictApplicationsDialog *)object; g_clear_pointer (&self->app_filter, mct_app_filter_unref); - g_clear_object (&self->user); + g_clear_pointer (&self->user_display_name, g_free); G_OBJECT_CLASS (mct_restrict_applications_dialog_parent_class)->dispose (object); } @@ -168,19 +169,22 @@ mct_restrict_applications_dialog_class_init (MctRestrictApplicationsDialogClass G_PARAM_EXPLICIT_NOTIFY); /** - * MctRestrictApplicationsDialog:user: (nullable) + * MctRestrictApplicationsDialog:user-display-name: (nullable) * - * The currently selected user account, or %NULL if no user is selected. + * The display name for the currently selected user account, or %NULL if no + * user is selected. This will typically be the user’s full name (if known) + * or their username. + * + * If set, it must be valid UTF-8 and non-empty. * * Since: 0.5.0 */ - properties[PROP_USER] = - g_param_spec_object ("user", - "User", - "The currently selected user account, or %NULL if no user is selected.", - ACT_TYPE_USER, + properties[PROP_USER_DISPLAY_NAME] = + g_param_spec_string ("user-display-name", + "User Display Name", + "The display name for the currently selected user account, or %NULL if no user is selected.", + NULL, G_PARAM_READWRITE | - G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); @@ -201,31 +205,12 @@ mct_restrict_applications_dialog_init (MctRestrictApplicationsDialog *self) gtk_widget_init_template (GTK_WIDGET (self)); } -static const gchar * -get_user_display_name (ActUser *user) -{ - const gchar *display_name; - - g_return_val_if_fail (ACT_IS_USER (user), _("unknown")); - - display_name = act_user_get_real_name (user); - if (display_name != NULL) - return display_name; - - display_name = act_user_get_user_name (user); - if (display_name != NULL) - return display_name; - - /* Translators: this is the full name for an unknown user account. */ - return _("unknown"); -} - static void update_description (MctRestrictApplicationsDialog *self) { g_autofree gchar *description = NULL; - if (self->user == NULL) + if (self->user_display_name == NULL) { gtk_widget_hide (GTK_WIDGET (self->description)); return; @@ -233,7 +218,7 @@ update_description (MctRestrictApplicationsDialog *self) /* Translators: the placeholder is a user’s full name */ description = g_strdup_printf (_("Allow %s to use the following installed applications."), - get_user_display_name (self->user)); + self->user_display_name); gtk_label_set_text (self->description, description); gtk_widget_show (GTK_WIDGET (self->description)); } @@ -241,7 +226,8 @@ update_description (MctRestrictApplicationsDialog *self) /** * mct_restrict_applications_dialog_new: * @app_filter: (transfer none): the initial app filter configuration to show - * @user: (transfer none) (nullable): the user to show the app filter for + * @user_display_name: (transfer none) (nullable): the display name of the user + * to show the app filter for, or %NULL if no user is selected * * Create a new #MctRestrictApplicationsDialog widget. * @@ -250,14 +236,16 @@ update_description (MctRestrictApplicationsDialog *self) */ MctRestrictApplicationsDialog * mct_restrict_applications_dialog_new (MctAppFilter *app_filter, - ActUser *user) + const gchar *user_display_name) { g_return_val_if_fail (app_filter != NULL, NULL); - g_return_val_if_fail (user == NULL || ACT_IS_USER (user), NULL); + g_return_val_if_fail (user_display_name == NULL || + (*user_display_name != '\0' && + g_utf8_validate (user_display_name, -1, NULL)), NULL); return g_object_new (MCT_TYPE_RESTRICT_APPLICATIONS_DIALOG, "app-filter", app_filter, - "user", user, + "user-display-name", user_display_name, NULL); } @@ -318,45 +306,50 @@ mct_restrict_applications_dialog_set_app_filter (MctRestrictApplicationsDialog * } /** - * mct_restrict_applications_dialog_get_user: + * mct_restrict_applications_dialog_get_user_display_name: * @self: an #MctRestrictApplicationsDialog * - * Get the value of #MctRestrictApplicationsDialog:user. + * Get the value of #MctRestrictApplicationsDialog:user-display-name. * - * Returns: (transfer none) (nullable): the user the dialog is configured for, - * or %NULL if unknown + * Returns: (transfer none) (nullable): the display name of the user the dialog + * is configured for, or %NULL if unknown * Since: 0.5.0 */ -ActUser * -mct_restrict_applications_dialog_get_user (MctRestrictApplicationsDialog *self) +const gchar * +mct_restrict_applications_dialog_get_user_display_name (MctRestrictApplicationsDialog *self) { g_return_val_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self), NULL); - return self->user; + return self->user_display_name; } /** - * mct_restrict_applications_dialog_set_user: + * mct_restrict_applications_dialog_set_user_display_name: * @self: an #MctRestrictApplicationsDialog - * @user: (nullable) (transfer none): the user to configure the dialog for, - * or %NULL if unknown + * @user_display_name: (nullable) (transfer none): the display name of the user + * to configure the dialog for, or %NULL if unknown * - * Set the value of #MctRestrictApplicationsDialog:user. + * Set the value of #MctRestrictApplicationsDialog:user-display-name. * * Since: 0.5.0 */ void -mct_restrict_applications_dialog_set_user (MctRestrictApplicationsDialog *self, - ActUser *user) +mct_restrict_applications_dialog_set_user_display_name (MctRestrictApplicationsDialog *self, + const gchar *user_display_name) { g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self)); - g_return_if_fail (user == NULL || ACT_IS_USER (user)); + g_return_if_fail (user_display_name == NULL || + (*user_display_name != '\0' && + g_utf8_validate (user_display_name, -1, NULL))); - if (g_set_object (&self->user, user)) - { - update_description (self); - g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER]); - } + if (g_strcmp0 (self->user_display_name, user_display_name) == 0) + return; + + g_clear_pointer (&self->user_display_name, g_free); + self->user_display_name = g_strdup (user_display_name); + + update_description (self); + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER_DISPLAY_NAME]); } /** diff --git a/libmalcontent-ui/restrict-applications-dialog.h b/libmalcontent-ui/restrict-applications-dialog.h index b4a5ed3..b96b335 100644 --- a/libmalcontent-ui/restrict-applications-dialog.h +++ b/libmalcontent-ui/restrict-applications-dialog.h @@ -21,7 +21,6 @@ #pragma once -#include #include #include #include @@ -34,15 +33,15 @@ G_BEGIN_DECLS G_DECLARE_FINAL_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, MCT, RESTRICT_APPLICATIONS_DIALOG, GtkDialog) MctRestrictApplicationsDialog *mct_restrict_applications_dialog_new (MctAppFilter *app_filter, - ActUser *user); + const gchar *user_display_name); MctAppFilter *mct_restrict_applications_dialog_get_app_filter (MctRestrictApplicationsDialog *self); void mct_restrict_applications_dialog_set_app_filter (MctRestrictApplicationsDialog *self, MctAppFilter *app_filter); -ActUser *mct_restrict_applications_dialog_get_user (MctRestrictApplicationsDialog *self); -void mct_restrict_applications_dialog_set_user (MctRestrictApplicationsDialog *self, - ActUser *user); +const gchar *mct_restrict_applications_dialog_get_user_display_name (MctRestrictApplicationsDialog *self); +void mct_restrict_applications_dialog_set_user_display_name (MctRestrictApplicationsDialog *self, + const gchar *user_display_name); void mct_restrict_applications_dialog_build_app_filter (MctRestrictApplicationsDialog *self, MctAppFilterBuilder *builder); diff --git a/libmalcontent-ui/restrict-applications-selector.h b/libmalcontent-ui/restrict-applications-selector.h index 702a594..e02a10a 100644 --- a/libmalcontent-ui/restrict-applications-selector.h +++ b/libmalcontent-ui/restrict-applications-selector.h @@ -21,7 +21,6 @@ #pragma once -#include #include #include #include diff --git a/libmalcontent-ui/user-controls.c b/libmalcontent-ui/user-controls.c index e9310fb..20ab0ba 100644 --- a/libmalcontent-ui/user-controls.c +++ b/libmalcontent-ui/user-controls.c @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -37,6 +38,38 @@ /* The value which we store as an age to indicate that OARS filtering is disabled. */ static const guint32 oars_disabled_age = (guint32) -1; +/** + * MctUserControls: + * + * A group of widgets which allow setting the parental controls for a given + * user. + * + * If #MctUserControls:user is set, the current parental controls settings for + * that user will be loaded and displayed, and any changes made via the controls + * will be automatically saved for that user (potentially after a short + * timeout). + * + * If #MctUserControls:user is unset (for example, if setting the parental + * controls for a user account which hasn’t yet been created), the controls can + * be initialised by setting: + * * #MctUserControls:app-filter + * * #MctUserControls:user-account-type + * * #MctUserControls:user-locale + * * #MctUserControls:user-display-name + * + * When #MctUserControls:user is unset, changes made to the parental controls + * cannot be saved automatically, and must be queried using + * mct_user_controls_build_app_filter(), then saved by the calling code. + * + * As parental controls are system settings, privileges are needed to view and + * edit them (for the current user or for other users). These can be acquired + * using polkit. #MctUserControls:permission is used to query the current + * permissions for getting/setting parental controls. If it’s %NULL, or if + * permissions are not currently granted, the #MctUserControls will be + * insensitive. + * + * Since: 0.5.0 + */ struct _MctUserControls { GtkGrid parent_instance; @@ -58,11 +91,15 @@ struct _MctUserControls GCancellable *cancellable; /* (owned) */ MctManager *manager; /* (owned) */ - MctAppFilter *filter; /* (owned) */ + MctAppFilter *filter; /* (owned) (nullable) */ guint selected_age; /* @oars_disabled_age to disable OARS */ guint blacklist_apps_source_id; gboolean flushed_on_dispose; + + ActUserAccountType user_account_type; + gchar *user_locale; /* (nullable) (owned) */ + gchar *user_display_name; /* (nullable) (owned) */ }; static gboolean blacklist_apps_cb (gpointer data); @@ -96,14 +133,17 @@ static void on_permission_allowed_cb (GObject *obj, G_DEFINE_TYPE (MctUserControls, mct_user_controls, GTK_TYPE_GRID) -enum +typedef enum { PROP_USER = 1, PROP_PERMISSION, - N_PROPS -}; + PROP_APP_FILTER, + PROP_USER_ACCOUNT_TYPE, + PROP_USER_LOCALE, + PROP_USER_DISPLAY_NAME, +} MctUserControlsProperty; -static GParamSpec *properties [N_PROPS]; +static GParamSpec *properties[PROP_USER_DISPLAY_NAME + 1]; static const GActionEntry actions[] = { { "set-age", on_set_age_action_activated, "u", NULL, NULL, { 0, }} @@ -146,13 +186,53 @@ static const gchar * const oars_categories[] = /* Auxiliary methods */ static GsContentRatingSystem -get_content_rating_system (ActUser *user) +get_content_rating_system (MctUserControls *self) { - const gchar *user_language; + if (self->user_locale == NULL) + return GS_CONTENT_RATING_SYSTEM_UNKNOWN; - user_language = act_user_get_language (user); + return gs_utils_content_rating_system_from_locale (self->user_locale); +} - return gs_utils_content_rating_system_from_locale (user_language); +static const gchar * +get_user_locale (ActUser *user) +{ + const gchar *locale; + + g_return_val_if_fail (ACT_IS_USER (user), "C"); + + /* accounts-service can return %NULL if loading over D-Bus failed. */ + locale = act_user_get_language (user); + if (locale == NULL) + return NULL; + + /* It can return the empty string if the user uses the system default locale. */ + if (*locale == '\0') + locale = setlocale (LC_MESSAGES, NULL); + + if (locale == NULL || *locale == '\0') + locale = "C"; + + return locale; +} + +static const gchar * +get_user_display_name (ActUser *user) +{ + const gchar *display_name; + + g_return_val_if_fail (ACT_IS_USER (user), _("unknown")); + + display_name = act_user_get_real_name (user); + if (display_name != NULL) + return display_name; + + display_name = act_user_get_user_name (user); + if (display_name != NULL) + return display_name; + + /* Translators: this is the full name for an unknown user account. */ + return _("unknown"); } static void @@ -180,12 +260,10 @@ flush_update_blacklisted_apps (MctUserControls *self) } static void -update_app_filter (MctUserControls *self) +update_app_filter_from_user (MctUserControls *self) { g_autoptr(GError) error = NULL; - g_clear_pointer (&self->filter, mct_app_filter_unref); - if (self->user == NULL) return; @@ -199,6 +277,7 @@ update_app_filter (MctUserControls *self) return; /* FIXME: make it asynchronous */ + g_clear_pointer (&self->filter, mct_app_filter_unref); self->filter = mct_manager_get_app_filter (self->manager, act_user_get_uid (self->user), MCT_MANAGER_GET_VALUE_FLAGS_NONE, @@ -226,7 +305,7 @@ update_categories_from_language (MctUserControls *self) gsize i; g_autofree gchar *disabled_action = NULL; - rating_system = get_content_rating_system (self->user); + rating_system = get_content_rating_system (self); rating_system_str = gs_content_rating_system_to_str (rating_system); g_debug ("Using rating system %s", rating_system_str); @@ -306,7 +385,7 @@ update_oars_level (MctUserControls *self) g_debug ("Effective age for this user: %u; %s", maximum_age, all_categories_unset ? "all categories unset" : "some categories set"); - rating_system = get_content_rating_system (self->user); + rating_system = get_content_rating_system (self); rating_age_category = gs_utils_content_rating_age_to_str (rating_system, maximum_age); /* Unrestricted? */ @@ -323,7 +402,7 @@ update_allow_app_installation (MctUserControls *self) gboolean allow_user_installation; gboolean non_admin_user = TRUE; - if (act_user_get_account_type (self->user) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR) + if (self->user_account_type == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR) non_admin_user = FALSE; /* Admins are always allowed to install apps for all users. This behaviour is governed @@ -334,8 +413,8 @@ update_allow_app_installation (MctUserControls *self) /* If user is admin, we are done here, bail out. */ if (!non_admin_user) { - g_debug ("User %s is administrator, hiding app installation controls", - act_user_get_user_name (self->user)); + g_debug ("User ‘%s’ is an administrator, hiding app installation controls", + self->user_display_name); return; } @@ -427,63 +506,16 @@ blacklist_apps_cb (gpointer data) g_autoptr(MctAppFilter) new_filter = NULL; g_autoptr(GError) error = NULL; MctUserControls *self = data; - gboolean allow_web_browsers; - gsize i; self->blacklist_apps_source_id = 0; - g_debug ("Building parental controls settings…"); - - /* Blacklist */ - - g_debug ("\t → Blacklisting apps"); - - mct_restrict_applications_dialog_build_app_filter (self->restrict_applications_dialog, &builder); - - /* Maturity level */ - - g_debug ("\t → Maturity level"); - - if (self->selected_age == oars_disabled_age) - g_debug ("\t\t → Disabled"); - - for (i = 0; self->selected_age != oars_disabled_age && oars_categories[i] != NULL; i++) + if (self->user == NULL) { - MctAppFilterOarsValue oars_value; - const gchar *oars_category; - - oars_category = oars_categories[i]; - oars_value = as_content_rating_id_csm_age_to_value (oars_category, self->selected_age); - - g_debug ("\t\t → %s: %s", oars_category, oars_value_to_string (oars_value)); - - mct_app_filter_builder_set_oars_value (&builder, oars_category, oars_value); - } - - /* Web browsers */ - allow_web_browsers = gtk_switch_get_active (self->allow_web_browsers_switch); - - g_debug ("\t → %s web browsers", allow_web_browsers ? "Enabling" : "Disabling"); - - if (!allow_web_browsers) - mct_app_filter_builder_blacklist_content_type (&builder, WEB_BROWSERS_CONTENT_TYPE); - - /* App installation */ - if (act_user_get_account_type (self->user) != ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR) - { - gboolean allow_system_installation; - gboolean allow_user_installation; - - allow_system_installation = gtk_switch_get_active (self->allow_system_installation_switch); - allow_user_installation = gtk_switch_get_active (self->allow_user_installation_switch); - - g_debug ("\t → %s system installation", allow_system_installation ? "Enabling" : "Disabling"); - g_debug ("\t → %s user installation", allow_user_installation ? "Enabling" : "Disabling"); - - mct_app_filter_builder_set_allow_user_installation (&builder, allow_user_installation); - mct_app_filter_builder_set_allow_system_installation (&builder, allow_system_installation); + g_debug ("Not saving app filter as user is unset"); + return G_SOURCE_REMOVE; } + mct_user_controls_build_app_filter (self, &builder); new_filter = mct_app_filter_builder_end (&builder); /* FIXME: should become asynchronous */ @@ -549,7 +581,7 @@ on_restrict_applications_button_clicked_cb (GtkButton *button, gtk_window_set_transient_for (GTK_WINDOW (self->restrict_applications_dialog), GTK_WINDOW (toplevel)); - mct_restrict_applications_dialog_set_user (self->restrict_applications_dialog, self->user); + mct_restrict_applications_dialog_set_user_display_name (self->restrict_applications_dialog, self->user_display_name); mct_restrict_applications_dialog_set_app_filter (self->restrict_applications_dialog, self->filter); gtk_widget_show (GTK_WIDGET (self->restrict_applications_dialog)); @@ -598,7 +630,7 @@ on_set_age_action_activated (GSimpleAction *action, self = MCT_USER_CONTROLS (user_data); age = g_variant_get_uint32 (param); - rating_system = get_content_rating_system (self->user); + rating_system = get_content_rating_system (self); entries = gs_utils_content_rating_get_values (rating_system); ages = gs_utils_content_rating_get_ages (rating_system); @@ -640,6 +672,8 @@ mct_user_controls_finalize (GObject *object) g_clear_object (&self->action_group); g_clear_object (&self->cancellable); g_clear_object (&self->user); + g_clear_pointer (&self->user_locale, g_free); + g_clear_pointer (&self->user_display_name, g_free); if (self->permission != NULL && self->permission_allowed_id != 0) { @@ -682,7 +716,7 @@ mct_user_controls_get_property (GObject *object, { MctUserControls *self = MCT_USER_CONTROLS (object); - switch (prop_id) + switch ((MctUserControlsProperty) prop_id) { case PROP_USER: g_value_set_object (value, self->user); @@ -692,6 +726,22 @@ mct_user_controls_get_property (GObject *object, g_value_set_object (value, self->permission); break; + case PROP_APP_FILTER: + g_value_set_boxed (value, self->filter); + break; + + case PROP_USER_ACCOUNT_TYPE: + g_value_set_enum (value, self->user_account_type); + break; + + case PROP_USER_LOCALE: + g_value_set_string (value, self->user_locale); + break; + + case PROP_USER_DISPLAY_NAME: + g_value_set_string (value, self->user_display_name); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -705,7 +755,7 @@ mct_user_controls_set_property (GObject *object, { MctUserControls *self = MCT_USER_CONTROLS (object); - switch (prop_id) + switch ((MctUserControlsProperty) prop_id) { case PROP_USER: mct_user_controls_set_user (self, g_value_get_object (value)); @@ -715,6 +765,22 @@ mct_user_controls_set_property (GObject *object, mct_user_controls_set_permission (self, g_value_get_object (value)); break; + case PROP_APP_FILTER: + mct_user_controls_set_app_filter (self, g_value_get_boxed (value)); + break; + + case PROP_USER_ACCOUNT_TYPE: + mct_user_controls_set_user_account_type (self, g_value_get_enum (value)); + break; + + case PROP_USER_LOCALE: + mct_user_controls_set_user_locale (self, g_value_get_string (value)); + break; + + case PROP_USER_DISPLAY_NAME: + mct_user_controls_set_user_display_name (self, g_value_get_string (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); } @@ -747,7 +813,92 @@ mct_user_controls_class_init (MctUserControlsClass *klass) G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY); - g_object_class_install_properties (object_class, N_PROPS, properties); + /** + * MctUserControls:app-filter: (nullable) + * + * The user’s current app filter, used to set up the user controls. As app + * filters are immutable, it is not updated as the user controls are changed. + * Use mct_user_controls_build_app_filter() to build the new app filter. + * + * This may be %NULL if the app filter is unknown, or if querying it from + * #MctUserControls:user fails. + * + * Since: 0.5.0 + */ + properties[PROP_APP_FILTER] = + g_param_spec_boxed ("app-filter", + "App Filter", + "The user’s current app filter, used to set up the user controls, or %NULL if unknown.", + MCT_TYPE_APP_FILTER, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * MctUserControls:user-account-type: + * + * The type of the currently selected user account. + * + * Since: 0.5.0 + */ + properties[PROP_USER_ACCOUNT_TYPE] = + g_param_spec_enum ("user-account-type", + "User Account Type", + "The type of the currently selected user account.", + /* FIXME: Not a typo here; libaccountsservice uses the wrong namespace. + * See: https://gitlab.freedesktop.org/accountsservice/accountsservice/issues/84 */ + ACT_USER_TYPE_USER_ACCOUNT_TYPE, + ACT_USER_ACCOUNT_TYPE_STANDARD, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * MctUserControls:user-locale: (nullable) + * + * The locale for the currently selected user account, or %NULL if no + * user is selected. + * + * If set, it must be in the format documented by [`setlocale()`](man:setlocale(3)): + * ``` + * language[_territory][.codeset][@modifier] + * ``` + * where `language` is an ISO 639 language code, `territory` is an ISO 3166 + * country code, and `codeset` is a character set or encoding identifier like + * `ISO-8859-1` or `UTF-8`. + * + * Since: 0.5.0 + */ + properties[PROP_USER_LOCALE] = + g_param_spec_string ("user-locale", + "User Locale", + "The locale for the currently selected user account, or %NULL if no user is selected.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * MctUserControls:user-display-name: (nullable) + * + * The display name for the currently selected user account, or %NULL if no + * user is selected. This will typically be the user’s full name (if known) + * or their username. + * + * If set, it must be valid UTF-8 and non-empty. + * + * Since: 0.5.0 + */ + properties[PROP_USER_DISPLAY_NAME] = + g_param_spec_string ("user-display-name", + "User Display Name", + "The display name for the currently selected user account, or %NULL if no user is selected.", + NULL, + G_PARAM_READWRITE | + G_PARAM_STATIC_STRINGS | + G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties); gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentUi/ui/user-controls.ui"); @@ -808,6 +959,16 @@ mct_user_controls_init (MctUserControls *self) G_BINDING_DEFAULT); } +/** + * mct_user_controls_get_user: + * @self: an #MctUserControls + * + * Get the value of #MctUserControls:user. + * + * Returns: (transfer none) (nullable): the user the controls are configured for, + * or %NULL if unknown + * Since: 0.5.0 + */ ActUser * mct_user_controls_get_user (MctUserControls *self) { @@ -816,6 +977,16 @@ mct_user_controls_get_user (MctUserControls *self) return self->user; } +/** + * mct_user_controls_set_user: + * @self: an #MctUserControls + * @user: (nullable) (transfer none): the user to configure the controls for, + * or %NULL if unknown + * + * Set the value of #MctUserControls:user. + * + * Since: 0.5.0 + */ void mct_user_controls_set_user (MctUserControls *self, ActUser *user) @@ -829,10 +1000,21 @@ mct_user_controls_set_user (MctUserControls *self, if (g_set_object (&self->user, user)) { - update_app_filter (self); + g_object_freeze_notify (G_OBJECT (self)); + + /* Update the starting widget state from the user. */ + if (user != NULL) + { + mct_user_controls_set_user_account_type (self, act_user_get_account_type (user)); + mct_user_controls_set_user_locale (self, get_user_locale (user)); + mct_user_controls_set_user_display_name (self, get_user_display_name (user)); + } + + update_app_filter_from_user (self); setup_parental_control_settings (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER]); + g_object_thaw_notify (G_OBJECT (self)); } } @@ -843,11 +1025,22 @@ on_permission_allowed_cb (GObject *obj, { MctUserControls *self = MCT_USER_CONTROLS (user_data); - update_app_filter (self); + update_app_filter_from_user (self); setup_parental_control_settings (self); } -GPermission * /* (nullable) */ +/** + * mct_user_controls_get_permission: + * @self: an #MctUserControls + * + * Get the value of #MctUserControls:permission. + * + * Returns: (transfer none) (nullable): a #GPermission indicating whether the + * current user has permission to view or change parental controls, or %NULL + * if permission is not allowed or is unknown + * Since: 0.5.0 + */ +GPermission * mct_user_controls_get_permission (MctUserControls *self) { g_return_val_if_fail (MCT_IS_USER_CONTROLS (self), NULL); @@ -855,9 +1048,20 @@ mct_user_controls_get_permission (MctUserControls *self) return self->permission; } +/** + * mct_user_controls_set_permission: + * @self: an #MctUserControls + * @permission: (nullable) (transfer none): the #GPermission indicating whether + * the current user has permission to view or change parental controls, or + * %NULL if permission is not allowed or is unknown + * + * Set the value of #MctUserControls:permission. + * + * Since: 0.5.0 + */ void mct_user_controls_set_permission (MctUserControls *self, - GPermission *permission /* (nullable) */) + GPermission *permission) { g_return_if_fail (MCT_IS_USER_CONTROLS (self)); g_return_if_fail (permission == NULL || G_IS_PERMISSION (permission)); @@ -883,8 +1087,287 @@ mct_user_controls_set_permission (MctUserControls *self, } /* Handle changes. */ - update_app_filter (self); + update_app_filter_from_user (self); setup_parental_control_settings (self); g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PERMISSION]); } + +/** + * mct_user_controls_get_app_filter: + * @self: an #MctUserControls + * + * Get the value of #MctUserControls:app-filter. If the app filter is unknown + * or could not be retrieved from #MctUserControls:user, this will be %NULL. + * + * Returns: (transfer none) (nullable): the initial app filter used to + * populate the user controls, or %NULL if unknown + * Since: 0.5.0 + */ +MctAppFilter * +mct_user_controls_get_app_filter (MctUserControls *self) +{ + g_return_val_if_fail (MCT_IS_USER_CONTROLS (self), NULL); + + return self->filter; +} + +/** + * mct_user_controls_set_app_filter: + * @self: an #MctUserControls + * @app_filter: (nullable) (transfer none): the app filter to configure the user + * controls from, or %NULL if unknown + * + * Set the value of #MctUserControls:app-filter. + * + * This will overwrite any user changes to the controls, so they should be saved + * first using mct_user_controls_build_app_filter() if desired. They will be + * saved automatically if #MctUserControls:user is set. + * + * Since: 0.5.0 + */ +void +mct_user_controls_set_app_filter (MctUserControls *self, + MctAppFilter *app_filter) +{ + g_return_if_fail (MCT_IS_USER_CONTROLS (self)); + + /* If we have pending unsaved changes from the previous configuration, force + * them to be saved first. */ + flush_update_blacklisted_apps (self); + + if (self->filter == app_filter) + return; + + g_clear_pointer (&self->filter, mct_app_filter_unref); + if (app_filter != NULL) + self->filter = mct_app_filter_ref (app_filter); + + g_debug ("Set new app filter from caller"); + setup_parental_control_settings (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APP_FILTER]); +} + +/** + * mct_user_controls_get_user_account_type: + * @self: an #MctUserControls + * + * Get the value of #MctUserControls:user-account-type. + * + * Returns: the account type of the user the controls are configured for + * Since: 0.5.0 + */ +ActUserAccountType +mct_user_controls_get_user_account_type (MctUserControls *self) +{ + g_return_val_if_fail (MCT_IS_USER_CONTROLS (self), ACT_USER_ACCOUNT_TYPE_STANDARD); + + return self->user_account_type; +} + +/** + * mct_user_controls_set_user_account_type: + * @self: an #MctUserControls + * @user_account_type: the account type of the user to configure the controls for + * + * Set the value of #MctUserControls:user-account-type. + * + * Since: 0.5.0 + */ +void +mct_user_controls_set_user_account_type (MctUserControls *self, + ActUserAccountType user_account_type) +{ + g_return_if_fail (MCT_IS_USER_CONTROLS (self)); + + /* If we have pending unsaved changes from the previous user, force them to be + * saved first. */ + flush_update_blacklisted_apps (self); + + if (self->user_account_type == user_account_type) + return; + + self->user_account_type = user_account_type; + + setup_parental_control_settings (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER_ACCOUNT_TYPE]); +} + +/** + * mct_user_controls_get_user_locale: + * @self: an #MctUserControls + * + * Get the value of #MctUserControls:user-locale. + * + * Returns: (transfer none) (nullable): the locale of the user the controls + * are configured for, or %NULL if unknown + * Since: 0.5.0 + */ +const gchar * +mct_user_controls_get_user_locale (MctUserControls *self) +{ + g_return_val_if_fail (MCT_IS_USER_CONTROLS (self), NULL); + + return self->user_locale; +} + +/** + * mct_user_controls_set_user_locale: + * @self: an #MctUserControls + * @user_locale: (nullable) (transfer none): the locale of the user + * to configure the controls for, or %NULL if unknown + * + * Set the value of #MctUserControls:user-locale. + * + * Since: 0.5.0 + */ +void +mct_user_controls_set_user_locale (MctUserControls *self, + const gchar *user_locale) +{ + g_return_if_fail (MCT_IS_USER_CONTROLS (self)); + g_return_if_fail (user_locale == NULL || + (*user_locale != '\0' && + g_utf8_validate (user_locale, -1, NULL))); + + /* If we have pending unsaved changes from the previous user, force them to be + * saved first. */ + flush_update_blacklisted_apps (self); + + if (g_strcmp0 (self->user_locale, user_locale) == 0) + return; + + g_clear_pointer (&self->user_locale, g_free); + self->user_locale = g_strdup (user_locale); + + setup_parental_control_settings (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER_LOCALE]); +} + +/** + * mct_user_controls_get_user_display_name: + * @self: an #MctUserControls + * + * Get the value of #MctUserControls:user-display-name. + * + * Returns: (transfer none) (nullable): the display name of the user the controls + * are configured for, or %NULL if unknown + * Since: 0.5.0 + */ +const gchar * +mct_user_controls_get_user_display_name (MctUserControls *self) +{ + g_return_val_if_fail (MCT_IS_USER_CONTROLS (self), NULL); + + return self->user_display_name; +} + +/** + * mct_user_controls_set_user_display_name: + * @self: an #MctUserControls + * @user_display_name: (nullable) (transfer none): the display name of the user + * to configure the controls for, or %NULL if unknown + * + * Set the value of #MctUserControls:user-display-name. + * + * Since: 0.5.0 + */ +void +mct_user_controls_set_user_display_name (MctUserControls *self, + const gchar *user_display_name) +{ + g_return_if_fail (MCT_IS_USER_CONTROLS (self)); + g_return_if_fail (user_display_name == NULL || + (*user_display_name != '\0' && + g_utf8_validate (user_display_name, -1, NULL))); + + /* If we have pending unsaved changes from the previous user, force them to be + * saved first. */ + flush_update_blacklisted_apps (self); + + if (g_strcmp0 (self->user_display_name, user_display_name) == 0) + return; + + g_clear_pointer (&self->user_display_name, g_free); + self->user_display_name = g_strdup (user_display_name); + + setup_parental_control_settings (self); + + g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER_DISPLAY_NAME]); +} + +/** + * mct_user_controls_build_app_filter: + * @self: an #MctUserControls + * @builder: an existing #MctAppFilterBuilder to modify + * + * Get the app filter settings currently configured in the user controls, by + * modifying the given @builder. This can be used to save the settings manually. + * + * Since: 0.5.0 + */ +void +mct_user_controls_build_app_filter (MctUserControls *self, + MctAppFilterBuilder *builder) +{ + gboolean allow_web_browsers; + gsize i; + + g_return_if_fail (MCT_IS_USER_CONTROLS (self)); + g_return_if_fail (builder != NULL); + + g_debug ("Building parental controls settings…"); + + /* Blacklist */ + + g_debug ("\t → Blacklisting apps"); + + mct_restrict_applications_dialog_build_app_filter (self->restrict_applications_dialog, builder); + + /* Maturity level */ + + g_debug ("\t → Maturity level"); + + if (self->selected_age == oars_disabled_age) + g_debug ("\t\t → Disabled"); + + for (i = 0; self->selected_age != oars_disabled_age && oars_categories[i] != NULL; i++) + { + MctAppFilterOarsValue oars_value; + const gchar *oars_category; + + oars_category = oars_categories[i]; + oars_value = as_content_rating_id_csm_age_to_value (oars_category, self->selected_age); + + g_debug ("\t\t → %s: %s", oars_category, oars_value_to_string (oars_value)); + + mct_app_filter_builder_set_oars_value (builder, oars_category, oars_value); + } + + /* Web browsers */ + allow_web_browsers = gtk_switch_get_active (self->allow_web_browsers_switch); + + g_debug ("\t → %s web browsers", allow_web_browsers ? "Enabling" : "Disabling"); + + if (!allow_web_browsers) + mct_app_filter_builder_blacklist_content_type (builder, WEB_BROWSERS_CONTENT_TYPE); + + /* App installation */ + if (self->user_account_type != ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR) + { + gboolean allow_system_installation; + gboolean allow_user_installation; + + allow_system_installation = gtk_switch_get_active (self->allow_system_installation_switch); + allow_user_installation = gtk_switch_get_active (self->allow_user_installation_switch); + + g_debug ("\t → %s system installation", allow_system_installation ? "Enabling" : "Disabling"); + g_debug ("\t → %s user installation", allow_user_installation ? "Enabling" : "Disabling"); + + mct_app_filter_builder_set_allow_user_installation (builder, allow_user_installation); + mct_app_filter_builder_set_allow_system_installation (builder, allow_system_installation); + } +} diff --git a/libmalcontent-ui/user-controls.h b/libmalcontent-ui/user-controls.h index a18d1c9..91e0863 100644 --- a/libmalcontent-ui/user-controls.h +++ b/libmalcontent-ui/user-controls.h @@ -23,7 +23,11 @@ #pragma once #include +#include +#include +#include #include +#include G_BEGIN_DECLS @@ -39,4 +43,23 @@ GPermission *mct_user_controls_get_permission (MctUserControls *self); void mct_user_controls_set_permission (MctUserControls *self, GPermission *permission); +MctAppFilter *mct_user_controls_get_app_filter (MctUserControls *self); +void mct_user_controls_set_app_filter (MctUserControls *self, + MctAppFilter *app_filter); + +ActUserAccountType mct_user_controls_get_user_account_type (MctUserControls *self); +void mct_user_controls_set_user_account_type (MctUserControls *self, + ActUserAccountType user_account_type); + +const gchar *mct_user_controls_get_user_locale (MctUserControls *self); +void mct_user_controls_set_user_locale (MctUserControls *self, + const gchar *user_locale); + +const gchar *mct_user_controls_get_user_display_name (MctUserControls *self); +void mct_user_controls_set_user_display_name (MctUserControls *self, + const gchar *user_display_name); + +void mct_user_controls_build_app_filter (MctUserControls *self, + MctAppFilterBuilder *builder); + G_END_DECLS diff --git a/libmalcontent/meson.build b/libmalcontent/meson.build index 1aa0c45..a312823 100644 --- a/libmalcontent/meson.build +++ b/libmalcontent/meson.build @@ -56,7 +56,7 @@ pkgconfig.generate(libmalcontent, libraries_private: libmalcontent_private_deps, ) -gnome.generate_gir(libmalcontent, +libmalcontent_gir = gnome.generate_gir(libmalcontent, sources: libmalcontent_sources + libmalcontent_headers + libmalcontent_private_headers, nsversion: libmalcontent_api_version, namespace: 'Malcontent',