Merge branch 'parental-controls-app-iteration' into 'master'
Iterate on UI of parental controls app See merge request pwithnall/malcontent!22
This commit is contained in:
commit
f64d4111f4
25
README.md
25
README.md
|
@ -11,12 +11,14 @@ being too violent.
|
||||||
It provides an
|
It provides an
|
||||||
[accounts-service](https://gitlab.freedesktop.org/accountsservice/accountsservice)
|
[accounts-service](https://gitlab.freedesktop.org/accountsservice/accountsservice)
|
||||||
vendor extension for storing an app filter to
|
vendor extension for storing an app filter to
|
||||||
restrict the child’s access to certain applications; and a simple library for
|
restrict the child’s access to certain applications; a simple library for
|
||||||
accessing and applying the app filter. This results in the policy being stored
|
accessing and applying the app filter; and a UI program (`malcontent-control`)
|
||||||
in `/var/lib/AccountsService/users/${user}`, which is a key file readable and
|
for viewing and changing the parental controls settings on users.
|
||||||
writable only by the accounts-service daemon. Access to the data is mediated
|
|
||||||
through accounts-service’s D-Bus interface, which libmalcontent is a client
|
The parental controls policy is stored in `/var/lib/AccountsService/users/${user}`,
|
||||||
library for.
|
which is a key file readable and writable only by the accounts-service daemon.
|
||||||
|
Access to the data is mediated through accounts-service’s D-Bus interface, which
|
||||||
|
libmalcontent is a client library for.
|
||||||
|
|
||||||
All the library APIs are currently unstable and are likely to change wildly.
|
All the library APIs are currently unstable and are likely to change wildly.
|
||||||
|
|
||||||
|
@ -74,15 +76,26 @@ $ flatpak run org.freedesktop.Bustle
|
||||||
error: Running app/org.freedesktop.Bustle/x86_64/stable is not allowed by the policy set by your administrator
|
error: Running app/org.freedesktop.Bustle/x86_64/stable is not allowed by the policy set by your administrator
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Development
|
||||||
|
-----------
|
||||||
|
|
||||||
|
When developing malcontent, you should be able to run an uninstalled version of
|
||||||
|
`malcontent-client` or `malcontent-control`, as long as the polkit files from
|
||||||
|
`accounts-service/` and `malcontent-control/org.freedesktop.MalcontentControl.policy.in`
|
||||||
|
have been installed system-wide (typically under `/usr/share/polkit-1`) where
|
||||||
|
your system copy of polkitd can find them.
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
------------
|
------------
|
||||||
|
|
||||||
* accounts-service
|
* accounts-service
|
||||||
* dbus-daemon
|
* dbus-daemon
|
||||||
|
* flatpak
|
||||||
* gio-2.0 ≥ 2.60
|
* gio-2.0 ≥ 2.60
|
||||||
* gio-unix-2.0 ≥ 2.60
|
* gio-unix-2.0 ≥ 2.60
|
||||||
* glib-2.0 ≥ 2.60
|
* glib-2.0 ≥ 2.60
|
||||||
* gobject-2.0 ≥ 2.60
|
* gobject-2.0 ≥ 2.60
|
||||||
|
* gtk+-3.0
|
||||||
* polkit-gobject-1
|
* polkit-gobject-1
|
||||||
|
|
||||||
Licensing
|
Licensing
|
||||||
|
|
|
@ -70,6 +70,7 @@ typedef enum
|
||||||
*/
|
*/
|
||||||
typedef struct _MctAppFilter MctAppFilter;
|
typedef struct _MctAppFilter MctAppFilter;
|
||||||
GType mct_app_filter_get_type (void);
|
GType mct_app_filter_get_type (void);
|
||||||
|
#define MCT_TYPE_APP_FILTER mct_app_filter_get_type ()
|
||||||
|
|
||||||
MctAppFilter *mct_app_filter_ref (MctAppFilter *filter);
|
MctAppFilter *mct_app_filter_ref (MctAppFilter *filter);
|
||||||
void mct_app_filter_unref (MctAppFilter *filter);
|
void mct_app_filter_unref (MctAppFilter *filter);
|
||||||
|
|
|
@ -44,6 +44,7 @@ G_BEGIN_DECLS
|
||||||
*/
|
*/
|
||||||
typedef struct _MctSessionLimits MctSessionLimits;
|
typedef struct _MctSessionLimits MctSessionLimits;
|
||||||
GType mct_session_limits_get_type (void);
|
GType mct_session_limits_get_type (void);
|
||||||
|
#define MCT_TYPE_SESSION_LIMITS mct_session_limits_get_type ()
|
||||||
|
|
||||||
MctSessionLimits *mct_session_limits_ref (MctSessionLimits *limits);
|
MctSessionLimits *mct_session_limits_ref (MctSessionLimits *limits);
|
||||||
void mct_session_limits_unref (MctSessionLimits *limits);
|
void mct_session_limits_unref (MctSessionLimits *limits);
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include <glib/gi18n-lib.h>
|
#include <glib/gi18n-lib.h>
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
#include <polkit/polkit.h>
|
||||||
|
|
||||||
#include "application.h"
|
#include "application.h"
|
||||||
#include "user-controls.h"
|
#include "user-controls.h"
|
||||||
|
@ -39,6 +40,14 @@ static void user_selector_notify_user_cb (GObject *obj,
|
||||||
static void user_manager_notify_is_loaded_cb (GObject *obj,
|
static void user_manager_notify_is_loaded_cb (GObject *obj,
|
||||||
GParamSpec *pspec,
|
GParamSpec *pspec,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
static void permission_new_cb (GObject *source_object,
|
||||||
|
GAsyncResult *result,
|
||||||
|
gpointer user_data);
|
||||||
|
static void permission_notify_allowed_cb (GObject *obj,
|
||||||
|
GParamSpec *pspec,
|
||||||
|
gpointer user_data);
|
||||||
|
static void user_accounts_panel_button_clicked_cb (GtkButton *button,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,13 +62,20 @@ struct _MctApplication
|
||||||
{
|
{
|
||||||
GtkApplication parent_instance;
|
GtkApplication parent_instance;
|
||||||
|
|
||||||
|
GCancellable *cancellable; /* (owned) */
|
||||||
|
|
||||||
ActUserManager *user_manager; /* (owned) */
|
ActUserManager *user_manager; /* (owned) */
|
||||||
|
|
||||||
|
GPermission *permission; /* (owned) */
|
||||||
|
GError *permission_error; /* (nullable) (owned) */
|
||||||
|
|
||||||
MctUserSelector *user_selector;
|
MctUserSelector *user_selector;
|
||||||
MctUserControls *user_controls;
|
MctUserControls *user_controls;
|
||||||
GtkStack *main_stack;
|
GtkStack *main_stack;
|
||||||
GtkLabel *error_title;
|
GtkLabel *error_title;
|
||||||
GtkLabel *error_message;
|
GtkLabel *error_message;
|
||||||
|
GtkLockButton *lock_button;
|
||||||
|
GtkButton *user_accounts_panel_button;
|
||||||
};
|
};
|
||||||
|
|
||||||
G_DEFINE_TYPE (MctApplication, mct_application, GTK_TYPE_APPLICATION)
|
G_DEFINE_TYPE (MctApplication, mct_application, GTK_TYPE_APPLICATION)
|
||||||
|
@ -67,7 +83,7 @@ G_DEFINE_TYPE (MctApplication, mct_application, GTK_TYPE_APPLICATION)
|
||||||
static void
|
static void
|
||||||
mct_application_init (MctApplication *self)
|
mct_application_init (MctApplication *self)
|
||||||
{
|
{
|
||||||
/* Nothing to do here. */
|
self->cancellable = g_cancellable_new ();
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -93,6 +109,8 @@ mct_application_dispose (GObject *object)
|
||||||
{
|
{
|
||||||
MctApplication *self = MCT_APPLICATION (object);
|
MctApplication *self = MCT_APPLICATION (object);
|
||||||
|
|
||||||
|
g_cancellable_cancel (self->cancellable);
|
||||||
|
|
||||||
if (self->user_manager != NULL)
|
if (self->user_manager != NULL)
|
||||||
{
|
{
|
||||||
g_signal_handlers_disconnect_by_func (self->user_manager,
|
g_signal_handlers_disconnect_by_func (self->user_manager,
|
||||||
|
@ -100,6 +118,16 @@ mct_application_dispose (GObject *object)
|
||||||
g_clear_object (&self->user_manager);
|
g_clear_object (&self->user_manager);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (self->permission != NULL)
|
||||||
|
{
|
||||||
|
g_signal_handlers_disconnect_by_func (self->permission,
|
||||||
|
permission_notify_allowed_cb, self);
|
||||||
|
g_clear_object (&self->permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error (&self->permission_error);
|
||||||
|
g_clear_object (&self->cancellable);
|
||||||
|
|
||||||
G_OBJECT_CLASS (mct_application_parent_class)->dispose (object);
|
G_OBJECT_CLASS (mct_application_parent_class)->dispose (object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +154,11 @@ mct_application_activate (GApplication *application)
|
||||||
g_type_ensure (MCT_TYPE_USER_CONTROLS);
|
g_type_ensure (MCT_TYPE_USER_CONTROLS);
|
||||||
g_type_ensure (MCT_TYPE_USER_SELECTOR);
|
g_type_ensure (MCT_TYPE_USER_SELECTOR);
|
||||||
|
|
||||||
|
/* Start loading the permission */
|
||||||
|
polkit_permission_new ("org.freedesktop.MalcontentControl.administration",
|
||||||
|
NULL, self->cancellable,
|
||||||
|
permission_new_cb, self);
|
||||||
|
|
||||||
builder = gtk_builder_new ();
|
builder = gtk_builder_new ();
|
||||||
|
|
||||||
g_assert (self->user_manager == NULL);
|
g_assert (self->user_manager == NULL);
|
||||||
|
@ -146,11 +179,16 @@ mct_application_activate (GApplication *application)
|
||||||
self->user_controls = MCT_USER_CONTROLS (gtk_builder_get_object (builder, "user_controls"));
|
self->user_controls = MCT_USER_CONTROLS (gtk_builder_get_object (builder, "user_controls"));
|
||||||
self->error_title = GTK_LABEL (gtk_builder_get_object (builder, "error_title"));
|
self->error_title = GTK_LABEL (gtk_builder_get_object (builder, "error_title"));
|
||||||
self->error_message = GTK_LABEL (gtk_builder_get_object (builder, "error_message"));
|
self->error_message = GTK_LABEL (gtk_builder_get_object (builder, "error_message"));
|
||||||
|
self->lock_button = GTK_LOCK_BUTTON (gtk_builder_get_object (builder, "lock_button"));
|
||||||
|
self->user_accounts_panel_button = GTK_BUTTON (gtk_builder_get_object (builder, "user_accounts_panel_button"));
|
||||||
|
|
||||||
/* Connect signals. */
|
/* Connect signals. */
|
||||||
g_signal_connect (self->user_selector, "notify::user",
|
g_signal_connect_object (self->user_selector, "notify::user",
|
||||||
G_CALLBACK (user_selector_notify_user_cb),
|
G_CALLBACK (user_selector_notify_user_cb),
|
||||||
self);
|
self, 0 /* flags */);
|
||||||
|
g_signal_connect_object (self->user_accounts_panel_button, "clicked",
|
||||||
|
G_CALLBACK (user_accounts_panel_button_clicked_cb),
|
||||||
|
self, 0 /* flags */);
|
||||||
g_signal_connect (self->user_manager, "notify::is-loaded",
|
g_signal_connect (self->user_manager, "notify::is-loaded",
|
||||||
G_CALLBACK (user_manager_notify_is_loaded_cb), self);
|
G_CALLBACK (user_manager_notify_is_loaded_cb), self);
|
||||||
|
|
||||||
|
@ -178,18 +216,74 @@ mct_application_class_init (MctApplicationClass *klass)
|
||||||
application_class->activate = mct_application_activate;
|
application_class->activate = mct_application_activate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
update_main_stack (MctApplication *self)
|
||||||
|
{
|
||||||
|
gboolean is_user_manager_loaded, is_permission_loaded, has_permission;
|
||||||
|
const gchar *new_page_name, *old_page_name;
|
||||||
|
GtkWidget *new_focus_widget;
|
||||||
|
ActUser *selected_user;
|
||||||
|
|
||||||
|
/* The implementation of #ActUserManager guarantees that once is-loaded is
|
||||||
|
* true, it is never reset to false. */
|
||||||
|
g_object_get (self->user_manager, "is-loaded", &is_user_manager_loaded, NULL);
|
||||||
|
is_permission_loaded = (self->permission != NULL || self->permission_error != NULL);
|
||||||
|
has_permission = (self->permission != NULL && g_permission_get_allowed (self->permission));
|
||||||
|
selected_user = mct_user_selector_get_user (self->user_selector);
|
||||||
|
|
||||||
|
/* Handle any loading errors (including those from getting the permission). */
|
||||||
|
if ((is_user_manager_loaded && act_user_manager_no_service (self->user_manager)) ||
|
||||||
|
self->permission_error != NULL)
|
||||||
|
{
|
||||||
|
gtk_label_set_label (self->error_title,
|
||||||
|
_("Failed to load user data from the system"));
|
||||||
|
gtk_label_set_label (self->error_message,
|
||||||
|
_("Please make sure that the AccountsService is installed and enabled."));
|
||||||
|
|
||||||
|
new_page_name = "error";
|
||||||
|
new_focus_widget = NULL;
|
||||||
|
}
|
||||||
|
else if (is_user_manager_loaded && selected_user == NULL)
|
||||||
|
{
|
||||||
|
new_page_name = "no-other-users";
|
||||||
|
new_focus_widget = GTK_WIDGET (self->user_accounts_panel_button);
|
||||||
|
}
|
||||||
|
else if (is_permission_loaded && !has_permission)
|
||||||
|
{
|
||||||
|
gtk_lock_button_set_permission (self->lock_button, self->permission);
|
||||||
|
mct_user_controls_set_permission (self->user_controls, self->permission);
|
||||||
|
|
||||||
|
new_page_name = "unlock";
|
||||||
|
new_focus_widget = GTK_WIDGET (self->lock_button);
|
||||||
|
}
|
||||||
|
else if (is_permission_loaded && is_user_manager_loaded)
|
||||||
|
{
|
||||||
|
mct_user_controls_set_user (self->user_controls, selected_user);
|
||||||
|
|
||||||
|
new_page_name = "controls";
|
||||||
|
new_focus_widget = GTK_WIDGET (self->user_selector);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new_page_name = "loading";
|
||||||
|
new_focus_widget = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
old_page_name = gtk_stack_get_visible_child_name (self->main_stack);
|
||||||
|
gtk_stack_set_visible_child_name (self->main_stack, new_page_name);
|
||||||
|
|
||||||
|
if (new_focus_widget != NULL && !g_str_equal (old_page_name, new_page_name))
|
||||||
|
gtk_widget_grab_focus (new_focus_widget);
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
user_selector_notify_user_cb (GObject *obj,
|
user_selector_notify_user_cb (GObject *obj,
|
||||||
GParamSpec *pspec,
|
GParamSpec *pspec,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
MctUserSelector *selector = MCT_USER_SELECTOR (obj);
|
|
||||||
MctApplication *self = MCT_APPLICATION (user_data);
|
MctApplication *self = MCT_APPLICATION (user_data);
|
||||||
ActUser *user;
|
|
||||||
|
|
||||||
user = mct_user_selector_get_user (selector);
|
update_main_stack (self);
|
||||||
|
|
||||||
mct_user_controls_set_user (self->user_controls, user);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -198,34 +292,61 @@ user_manager_notify_is_loaded_cb (GObject *obj,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
MctApplication *self = MCT_APPLICATION (user_data);
|
MctApplication *self = MCT_APPLICATION (user_data);
|
||||||
ActUserManager *user_manager = ACT_USER_MANAGER (obj);
|
|
||||||
gboolean is_loaded;
|
|
||||||
const gchar *new_page_name;
|
|
||||||
|
|
||||||
/* The implementation of #ActUserManager guarantees that once is-loaded is
|
update_main_stack (self);
|
||||||
* true, it is never reset to false. */
|
}
|
||||||
g_object_get (user_manager, "is-loaded", &is_loaded, NULL);
|
|
||||||
|
|
||||||
/* Handle any loading errors. */
|
static void
|
||||||
if (is_loaded && act_user_manager_no_service (user_manager))
|
permission_new_cb (GObject *source_object,
|
||||||
|
GAsyncResult *result,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
MctApplication *self = MCT_APPLICATION (user_data);
|
||||||
|
g_autoptr(GPermission) permission = NULL;
|
||||||
|
g_autoptr(GError) local_error = NULL;
|
||||||
|
|
||||||
|
permission = polkit_permission_new_finish (result, &local_error);
|
||||||
|
if (permission == NULL)
|
||||||
{
|
{
|
||||||
gtk_label_set_label (self->error_title,
|
g_assert (self->permission_error == NULL);
|
||||||
_("Failed to load user data from the system"));
|
self->permission_error = g_steal_pointer (&local_error);
|
||||||
gtk_label_set_label (self->error_message,
|
g_debug ("Error getting permission: %s", self->permission_error->message);
|
||||||
_("Please make sure that the AccountsService is installed and enabled."));
|
|
||||||
|
|
||||||
new_page_name = "error";
|
|
||||||
}
|
|
||||||
else if (is_loaded)
|
|
||||||
{
|
|
||||||
new_page_name = "controls";
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
new_page_name = "loading";
|
g_assert (self->permission == NULL);
|
||||||
|
self->permission = g_steal_pointer (&permission);
|
||||||
|
|
||||||
|
g_signal_connect (self->permission, "notify::allowed",
|
||||||
|
G_CALLBACK (permission_notify_allowed_cb), self);
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_stack_set_visible_child_name (self->main_stack, new_page_name);
|
/* Recalculate the UI. */
|
||||||
|
update_main_stack (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
permission_notify_allowed_cb (GObject *obj,
|
||||||
|
GParamSpec *pspec,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
MctApplication *self = MCT_APPLICATION (user_data);
|
||||||
|
|
||||||
|
update_main_stack (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
user_accounts_panel_button_clicked_cb (GtkButton *button,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
g_autoptr(GError) local_error = NULL;
|
||||||
|
|
||||||
|
if (!g_spawn_command_line_async ("gnome-control-center user-accounts", &local_error))
|
||||||
|
{
|
||||||
|
g_warning ("Error opening GNOME Control Center: %s",
|
||||||
|
local_error->message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -97,12 +97,13 @@ mct_carousel_item_get_x (MctCarouselItem *item,
|
||||||
widget = GTK_WIDGET (item);
|
widget = GTK_WIDGET (item);
|
||||||
|
|
||||||
width = gtk_widget_get_allocated_width (widget);
|
width = gtk_widget_get_allocated_width (widget);
|
||||||
gtk_widget_translate_coordinates (widget,
|
if (!gtk_widget_translate_coordinates (widget,
|
||||||
parent,
|
parent,
|
||||||
width / 2,
|
width / 2,
|
||||||
0,
|
0,
|
||||||
&dest_x,
|
&dest_x,
|
||||||
NULL);
|
NULL))
|
||||||
|
return 0;
|
||||||
|
|
||||||
return CLAMP (dest_x - ARROW_SIZE,
|
return CLAMP (dest_x - ARROW_SIZE,
|
||||||
0,
|
0,
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<requires lib="gtk+" version="3.12"/>
|
<requires lib="gtk+" version="3.12"/>
|
||||||
<object class="GtkApplicationWindow" id="main_window">
|
<object class="GtkApplicationWindow" id="main_window">
|
||||||
<property name="default-width">500</property>
|
<property name="default-width">500</property>
|
||||||
<property name="default-height">700</property>
|
<property name="default-height">600</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkStack" id="main_stack">
|
<object class="GtkStack" id="main_stack">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -17,6 +17,7 @@
|
||||||
<object class="MctUserSelector" id="user_selector">
|
<object class="MctUserSelector" id="user_selector">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="user-manager">user_manager</property>
|
<property name="user-manager">user_manager</property>
|
||||||
|
<property name="show-administrators">False</property>
|
||||||
<accessibility>
|
<accessibility>
|
||||||
<relation target="user_controls" type="controller-for"/>
|
<relation target="user_controls" type="controller-for"/>
|
||||||
</accessibility>
|
</accessibility>
|
||||||
|
@ -30,7 +31,7 @@
|
||||||
<object class="GtkScrolledWindow">
|
<object class="GtkScrolledWindow">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="hscrollbar-policy">never</property>
|
<property name="hscrollbar-policy">never</property>
|
||||||
<property name="min-content-height">450</property>
|
<property name="min-content-height">350</property>
|
||||||
<child>
|
<child>
|
||||||
<object class="MctUserControls" id="user_controls">
|
<object class="MctUserControls" id="user_controls">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -48,6 +49,121 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<child type="center">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<property name="margin">18</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Permission Required</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="scale" value="1.4"/>
|
||||||
|
</attributes>
|
||||||
|
<child internal-child="accessible">
|
||||||
|
<object class="AtkObject">
|
||||||
|
<property name="AtkObject::accessible-role">static</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Permission is required to view and change parental controls settings for other users.</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<child internal-child="accessible">
|
||||||
|
<object class="AtkObject">
|
||||||
|
<property name="AtkObject::accessible-role">static</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLockButton" id="lock_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="can-default">True</property>
|
||||||
|
<property name="has-default">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">unlock</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="vexpand">True</property>
|
||||||
|
<child type="center">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<property name="margin">18</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">No Child Users Configured</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="scale" value="1.4"/>
|
||||||
|
</attributes>
|
||||||
|
<child internal-child="accessible">
|
||||||
|
<object class="AtkObject">
|
||||||
|
<property name="AtkObject::accessible-role">static</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">No child users are currently set up on the system. Create one before setting up their parental controls.</property>
|
||||||
|
<property name="wrap">True</property>
|
||||||
|
<child internal-child="accessible">
|
||||||
|
<object class="AtkObject">
|
||||||
|
<property name="AtkObject::accessible-role">static</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="user_accounts_panel_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">Create _Child User</property>
|
||||||
|
<property name="halign">center</property>
|
||||||
|
<property name="can-default">True</property>
|
||||||
|
<property name="has-default">True</property>
|
||||||
|
<property name="use-underline">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<style>
|
||||||
|
<class name="suggested-action"/>
|
||||||
|
</style>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="name">no-other-users</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkBox">
|
<object class="GtkBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Copyright © 2019 Endless Mobile, Inc. -->
|
<!-- Copyright © 2019 2020 Endless Mobile, Inc. -->
|
||||||
<gresources>
|
<gresources>
|
||||||
<gresource prefix="/org/freedesktop/MalcontentControl/ui">
|
<gresource prefix="/org/freedesktop/MalcontentControl/ui">
|
||||||
<file>carousel.css</file>
|
<file>carousel.css</file>
|
||||||
<file preprocess="xml-stripblanks">carousel.ui</file>
|
<file preprocess="xml-stripblanks">carousel.ui</file>
|
||||||
<file preprocess="xml-stripblanks">main.ui</file>
|
<file preprocess="xml-stripblanks">main.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">restrict-applications-dialog.ui</file>
|
||||||
|
<file preprocess="xml-stripblanks">restrict-applications-selector.ui</file>
|
||||||
<file preprocess="xml-stripblanks">user-controls.ui</file>
|
<file preprocess="xml-stripblanks">user-controls.ui</file>
|
||||||
<file preprocess="xml-stripblanks">user-selector.ui</file>
|
<file preprocess="xml-stripblanks">user-selector.ui</file>
|
||||||
</gresource>
|
</gresource>
|
||||||
|
|
|
@ -19,6 +19,10 @@ malcontent_control = executable('malcontent-control',
|
||||||
'gs-content-rating.c',
|
'gs-content-rating.c',
|
||||||
'gs-content-rating.h',
|
'gs-content-rating.h',
|
||||||
'main.c',
|
'main.c',
|
||||||
|
'restrict-applications-dialog.c',
|
||||||
|
'restrict-applications-dialog.h',
|
||||||
|
'restrict-applications-selector.c',
|
||||||
|
'restrict-applications-selector.h',
|
||||||
'user-controls.c',
|
'user-controls.c',
|
||||||
'user-controls.h',
|
'user-controls.h',
|
||||||
'user-image.c',
|
'user-image.c',
|
||||||
|
@ -33,6 +37,7 @@ malcontent_control = executable('malcontent-control',
|
||||||
dependency('gobject-2.0', version: '>= 2.54'),
|
dependency('gobject-2.0', version: '>= 2.54'),
|
||||||
dependency('gtk+-3.0'),
|
dependency('gtk+-3.0'),
|
||||||
dependency('flatpak'),
|
dependency('flatpak'),
|
||||||
|
dependency('polkit-gobject-1'),
|
||||||
libmalcontent_dep,
|
libmalcontent_dep,
|
||||||
],
|
],
|
||||||
include_directories: root_inc,
|
include_directories: root_inc,
|
||||||
|
@ -90,6 +95,8 @@ if xmllint.found()
|
||||||
files(
|
files(
|
||||||
'carousel.ui',
|
'carousel.ui',
|
||||||
'main.ui',
|
'main.ui',
|
||||||
|
'restrict-applications-dialog.ui',
|
||||||
|
'restrict-applications-selector.ui',
|
||||||
'user-controls.ui',
|
'user-controls.ui',
|
||||||
'user-selector.ui',
|
'user-selector.ui',
|
||||||
),
|
),
|
||||||
|
@ -98,6 +105,24 @@ if xmllint.found()
|
||||||
)
|
)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
policy_file = i18n.merge_file('policy-file',
|
||||||
|
input: '@0@.policy.in'.format(application_id),
|
||||||
|
output: '@0@.policy'.format(application_id),
|
||||||
|
po_dir: join_paths(meson.source_root(), 'po'),
|
||||||
|
install: true,
|
||||||
|
install_dir: join_paths(get_option('datadir'), 'polkit-1', 'actions'),
|
||||||
|
)
|
||||||
|
if xmllint.found()
|
||||||
|
test(
|
||||||
|
'validate-policy', xmllint,
|
||||||
|
args: [
|
||||||
|
'--nonet', '--noblanks', '--noout',
|
||||||
|
policy_file,
|
||||||
|
],
|
||||||
|
suite: ['malcontent-control'],
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
# FIXME: Add icons and tests
|
# FIXME: Add icons and tests
|
||||||
#subdir('icons')
|
#subdir('icons')
|
||||||
#subdir('tests')
|
#subdir('tests')
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD polkit Policy Configuration 1.0//EN" "http://www.freedesktop.org/software/polkit/policyconfig-1.dtd">
|
||||||
|
<!-- Copyright © 2020 Endless Mobile, Inc. -->
|
||||||
|
<policyconfig>
|
||||||
|
<vendor>The Malcontent Project</vendor>
|
||||||
|
<vendor_url>https://gitlab.freedesktop.org/pwithnall/malcontent</vendor_url>
|
||||||
|
|
||||||
|
<action id="org.freedesktop.MalcontentControl.administration">
|
||||||
|
<description>Manage parental controls</description>
|
||||||
|
<message>Authentication is required to read and change user parental controls</message>
|
||||||
|
<defaults>
|
||||||
|
<allow_any>no</allow_any>
|
||||||
|
<allow_inactive>no</allow_inactive>
|
||||||
|
<allow_active>auth_admin_keep</allow_active>
|
||||||
|
</defaults>
|
||||||
|
<annotate key="org.freedesktop.policykit.imply">com.endlessm.ParentalControls.AppFilter.ReadAny com.endlessm.ParentalControls.AppFilter.ChangeAny</annotate>
|
||||||
|
</action>
|
||||||
|
</policyconfig>
|
|
@ -0,0 +1,382 @@
|
||||||
|
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||||
|
*
|
||||||
|
* Copyright © 2020 Endless Mobile, 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* - Philip Withnall <withnall@endlessm.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <act/act.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <glib/gi18n.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#include "restrict-applications-dialog.h"
|
||||||
|
#include "restrict-applications-selector.h"
|
||||||
|
|
||||||
|
|
||||||
|
static void update_description (MctRestrictApplicationsDialog *self);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MctRestrictApplicationsDialog:
|
||||||
|
*
|
||||||
|
* The ‘Restrict Applications’ dialog is a dialog which shows the available
|
||||||
|
* applications on the system alongside a column of toggle switches, which
|
||||||
|
* allows the given user to be prevented from running each application.
|
||||||
|
*
|
||||||
|
* The dialog contains a single #MctRestrictApplicationsSelector. It takes a
|
||||||
|
* #MctRestrictApplicationsDialog:user and
|
||||||
|
* #MctRestrictApplicationsDialog:app-filter as input to set up the UI, and
|
||||||
|
* returns its output as set of modifications to a given #MctAppFilterBuilder
|
||||||
|
* using mct_restrict_applications_dialog_build_app_filter().
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
struct _MctRestrictApplicationsDialog
|
||||||
|
{
|
||||||
|
GtkDialog parent_instance;
|
||||||
|
|
||||||
|
MctRestrictApplicationsSelector *selector;
|
||||||
|
GtkLabel *description;
|
||||||
|
|
||||||
|
MctAppFilter *app_filter; /* (owned) (not nullable) */
|
||||||
|
ActUser *user; /* (owned) (nullable) */
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (MctRestrictApplicationsDialog, mct_restrict_applications_dialog, GTK_TYPE_DIALOG)
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
PROP_APP_FILTER = 1,
|
||||||
|
PROP_USER,
|
||||||
|
} MctRestrictApplicationsDialogProperty;
|
||||||
|
|
||||||
|
static GParamSpec *properties[PROP_USER + 1];
|
||||||
|
|
||||||
|
static void
|
||||||
|
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_OBJECT_CLASS (mct_restrict_applications_dialog_parent_class)->constructed (obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_dialog_get_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
MctRestrictApplicationsDialog *self = MCT_RESTRICT_APPLICATIONS_DIALOG (object);
|
||||||
|
|
||||||
|
switch ((MctRestrictApplicationsDialogProperty) prop_id)
|
||||||
|
{
|
||||||
|
case PROP_APP_FILTER:
|
||||||
|
g_value_set_boxed (value, self->app_filter);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case PROP_USER:
|
||||||
|
g_value_set_object (value, self->user);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_dialog_set_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
MctRestrictApplicationsDialog *self = MCT_RESTRICT_APPLICATIONS_DIALOG (object);
|
||||||
|
|
||||||
|
switch ((MctRestrictApplicationsDialogProperty) prop_id)
|
||||||
|
{
|
||||||
|
case PROP_APP_FILTER:
|
||||||
|
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));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
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_OBJECT_CLASS (mct_restrict_applications_dialog_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_dialog_class_init (MctRestrictApplicationsDialogClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->constructed = mct_restrict_applications_dialog_constructed;
|
||||||
|
object_class->get_property = mct_restrict_applications_dialog_get_property;
|
||||||
|
object_class->set_property = mct_restrict_applications_dialog_set_property;
|
||||||
|
object_class->dispose = mct_restrict_applications_dialog_dispose;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MctRestrictApplicationsDialog:app-filter: (not nullable)
|
||||||
|
*
|
||||||
|
* The user’s current app filter, used to set up the dialog. As app filters
|
||||||
|
* are immutable, it is not updated as the dialog is changed. Use
|
||||||
|
* mct_restrict_applications_dialog_build_app_filter() to build the new app
|
||||||
|
* filter.
|
||||||
|
*
|
||||||
|
* 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 dialog.",
|
||||||
|
MCT_TYPE_APP_FILTER,
|
||||||
|
G_PARAM_READWRITE |
|
||||||
|
G_PARAM_CONSTRUCT_ONLY |
|
||||||
|
G_PARAM_STATIC_STRINGS |
|
||||||
|
G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MctRestrictApplicationsDialog:user: (nullable)
|
||||||
|
*
|
||||||
|
* The currently selected user account, or %NULL if no user is selected.
|
||||||
|
*
|
||||||
|
* 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,
|
||||||
|
G_PARAM_READWRITE |
|
||||||
|
G_PARAM_CONSTRUCT_ONLY |
|
||||||
|
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/MalcontentControl/ui/restrict-applications-dialog.ui");
|
||||||
|
|
||||||
|
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, selector);
|
||||||
|
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsDialog, description);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_dialog_init (MctRestrictApplicationsDialog *self)
|
||||||
|
{
|
||||||
|
/* Ensure the types used in the UI are registered. */
|
||||||
|
g_type_ensure (MCT_TYPE_RESTRICT_APPLICATIONS_SELECTOR);
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
gtk_widget_hide (GTK_WIDGET (self->description));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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));
|
||||||
|
gtk_label_set_text (self->description, description);
|
||||||
|
gtk_widget_show (GTK_WIDGET (self->description));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* Create a new #MctRestrictApplicationsDialog widget.
|
||||||
|
*
|
||||||
|
* Returns: (transfer full): a new restricted applications editing dialog
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
MctRestrictApplicationsDialog *
|
||||||
|
mct_restrict_applications_dialog_new (MctAppFilter *app_filter,
|
||||||
|
ActUser *user)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (app_filter != NULL, NULL);
|
||||||
|
g_return_val_if_fail (user == NULL || ACT_IS_USER (user), NULL);
|
||||||
|
|
||||||
|
return g_object_new (MCT_TYPE_RESTRICT_APPLICATIONS_DIALOG,
|
||||||
|
"app-filter", app_filter,
|
||||||
|
"user", user,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_dialog_get_app_filter:
|
||||||
|
* @self: an #MctRestrictApplicationsDialog
|
||||||
|
*
|
||||||
|
* Get the value of #MctRestrictApplicationsDialog:app-filter. If the property
|
||||||
|
* was originally set to %NULL, this will be the empty app filter.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none) (not nullable): the initial app filter used to
|
||||||
|
* populate the dialog
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
MctAppFilter *
|
||||||
|
mct_restrict_applications_dialog_get_app_filter (MctRestrictApplicationsDialog *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self), NULL);
|
||||||
|
|
||||||
|
return self->app_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_dialog_set_app_filter:
|
||||||
|
* @self: an #MctRestrictApplicationsDialog
|
||||||
|
* @app_filter: (nullable) (transfer none): the app filter to configure the dialog
|
||||||
|
* from, or %NULL to use an empty app filter
|
||||||
|
*
|
||||||
|
* Set the value of #MctRestrictApplicationsDialog:app-filter.
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
mct_restrict_applications_dialog_set_app_filter (MctRestrictApplicationsDialog *self,
|
||||||
|
MctAppFilter *app_filter)
|
||||||
|
{
|
||||||
|
g_autoptr(MctAppFilter) owned_app_filter = NULL;
|
||||||
|
|
||||||
|
g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self));
|
||||||
|
|
||||||
|
/* Default app filter, typically for when we’re instantiated by #GtkBuilder. */
|
||||||
|
if (app_filter == NULL)
|
||||||
|
{
|
||||||
|
g_auto(MctAppFilterBuilder) builder = MCT_APP_FILTER_BUILDER_INIT ();
|
||||||
|
owned_app_filter = mct_app_filter_builder_end (&builder);
|
||||||
|
app_filter = owned_app_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app_filter == self->app_filter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_clear_pointer (&self->app_filter, mct_app_filter_unref);
|
||||||
|
self->app_filter = mct_app_filter_ref (app_filter);
|
||||||
|
|
||||||
|
mct_restrict_applications_selector_set_app_filter (self->selector, self->app_filter);
|
||||||
|
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APP_FILTER]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_dialog_get_user:
|
||||||
|
* @self: an #MctRestrictApplicationsDialog
|
||||||
|
*
|
||||||
|
* Get the value of #MctRestrictApplicationsDialog:user.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none) (nullable): the user the dialog is configured for,
|
||||||
|
* or %NULL if unknown
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
ActUser *
|
||||||
|
mct_restrict_applications_dialog_get_user (MctRestrictApplicationsDialog *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self), NULL);
|
||||||
|
|
||||||
|
return self->user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_dialog_set_user:
|
||||||
|
* @self: an #MctRestrictApplicationsDialog
|
||||||
|
* @user: (nullable) (transfer none): the user to configure the dialog for,
|
||||||
|
* or %NULL if unknown
|
||||||
|
*
|
||||||
|
* Set the value of #MctRestrictApplicationsDialog:user.
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
mct_restrict_applications_dialog_set_user (MctRestrictApplicationsDialog *self,
|
||||||
|
ActUser *user)
|
||||||
|
{
|
||||||
|
g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self));
|
||||||
|
g_return_if_fail (user == NULL || ACT_IS_USER (user));
|
||||||
|
|
||||||
|
if (g_set_object (&self->user, user))
|
||||||
|
{
|
||||||
|
update_description (self);
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_dialog_build_app_filter:
|
||||||
|
* @self: an #MctRestrictApplicationsDialog
|
||||||
|
* @builder: an existing #MctAppFilterBuilder to modify
|
||||||
|
*
|
||||||
|
* Get the app filter settings currently configured in the dialog, by modifying
|
||||||
|
* the given @builder.
|
||||||
|
*
|
||||||
|
* Typically this will be called in the handler for #GtkDialog::response.
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
mct_restrict_applications_dialog_build_app_filter (MctRestrictApplicationsDialog *self,
|
||||||
|
MctAppFilterBuilder *builder)
|
||||||
|
{
|
||||||
|
g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_DIALOG (self));
|
||||||
|
g_return_if_fail (builder != NULL);
|
||||||
|
|
||||||
|
mct_restrict_applications_selector_build_app_filter (self->selector, builder);
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||||
|
*
|
||||||
|
* Copyright © 2020 Endless Mobile, 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* - Philip Withnall <withnall@endlessm.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <act/act.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <libmalcontent/manager.h>
|
||||||
|
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define MCT_TYPE_RESTRICT_APPLICATIONS_DIALOG (mct_restrict_applications_dialog_get_type ())
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
void mct_restrict_applications_dialog_build_app_filter (MctRestrictApplicationsDialog *self,
|
||||||
|
MctAppFilterBuilder *builder);
|
||||||
|
|
||||||
|
G_END_DECLS
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Copyright © 2020 Endless, Inc. -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.12"/>
|
||||||
|
<template class="MctRestrictApplicationsDialog" parent="GtkDialog">
|
||||||
|
<property name="title" translatable="yes">Restrict Applications</property>
|
||||||
|
<property name="skip-taskbar-hint">True</property>
|
||||||
|
<property name="default-width">300</property>
|
||||||
|
<property name="default-height">500</property>
|
||||||
|
<child internal-child="headerbar">
|
||||||
|
<object class="GtkHeaderBar">
|
||||||
|
<property name="title" translatable="yes">Restrict Applications</property>
|
||||||
|
<property name="show-close-button">False</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child internal-child="vbox">
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="margin">18</property>
|
||||||
|
<property name="spacing">12</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="description">
|
||||||
|
<!-- Translated dynamically: -->
|
||||||
|
<property name="label">Allow {username} to use the following installed applications.</property>
|
||||||
|
<property name="visible">False</property>
|
||||||
|
<child internal-child="accessible">
|
||||||
|
<object class="AtkObject">
|
||||||
|
<property name="accessible-role">static</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="MctRestrictApplicationsSelector" id="selector">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="action">
|
||||||
|
<object class="GtkButton" id="button_save">
|
||||||
|
<property name="can-default">True</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">_Save</property>
|
||||||
|
<property name="use-underline">True</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<action-widgets>
|
||||||
|
<action-widget response="apply" default="true">button_save</action-widget>
|
||||||
|
</action-widgets>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -0,0 +1,670 @@
|
||||||
|
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||||
|
*
|
||||||
|
* Copyright © 2020 Endless Mobile, 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* - Philip Withnall <withnall@endlessm.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <flatpak.h>
|
||||||
|
#include <gio/gdesktopappinfo.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <glib/gi18n.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <libmalcontent/app-filter.h>
|
||||||
|
|
||||||
|
#include "restrict-applications-selector.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define WEB_BROWSERS_CONTENT_TYPE "x-scheme-handler/http"
|
||||||
|
|
||||||
|
static void app_info_changed_cb (GAppInfoMonitor *monitor,
|
||||||
|
gpointer user_data);
|
||||||
|
static void reload_apps (MctRestrictApplicationsSelector *self);
|
||||||
|
static GtkWidget *create_row_for_app_cb (gpointer item,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MctRestrictApplicationsSelector:
|
||||||
|
*
|
||||||
|
* The ‘Restrict Applications’ selector is a list box which shows the available
|
||||||
|
* applications on the system alongside a column of toggle switches, which
|
||||||
|
* allows the given user to be prevented from running each application.
|
||||||
|
*
|
||||||
|
* The selector takes an #MctRestrictApplicationsSelector:app-filter as input
|
||||||
|
* to set up the UI, and returns its output as set of modifications to a given
|
||||||
|
* #MctAppFilterBuilder using
|
||||||
|
* mct_restrict_applications_selector_build_app_filter().
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
struct _MctRestrictApplicationsSelector
|
||||||
|
{
|
||||||
|
GtkBox parent_instance;
|
||||||
|
|
||||||
|
GtkListBox *listbox;
|
||||||
|
|
||||||
|
GListStore *apps; /* (owned) */
|
||||||
|
GAppInfoMonitor *app_info_monitor; /* (owned) */
|
||||||
|
gulong app_info_monitor_changed_id;
|
||||||
|
GHashTable *blacklisted_apps; /* (owned) (element-type GAppInfo) */
|
||||||
|
|
||||||
|
MctAppFilter *app_filter; /* (owned) */
|
||||||
|
|
||||||
|
FlatpakInstallation *system_installation; /* (owned) */
|
||||||
|
FlatpakInstallation *user_installation; /* (owned) */
|
||||||
|
};
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (MctRestrictApplicationsSelector, mct_restrict_applications_selector, GTK_TYPE_BOX)
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
PROP_APP_FILTER = 1,
|
||||||
|
} MctRestrictApplicationsSelectorProperty;
|
||||||
|
|
||||||
|
static GParamSpec *properties[PROP_APP_FILTER + 1];
|
||||||
|
|
||||||
|
enum {
|
||||||
|
SIGNAL_CHANGED,
|
||||||
|
};
|
||||||
|
|
||||||
|
static guint signals[SIGNAL_CHANGED + 1];
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_selector_constructed (GObject *obj)
|
||||||
|
{
|
||||||
|
MctRestrictApplicationsSelector *self = MCT_RESTRICT_APPLICATIONS_SELECTOR (obj);
|
||||||
|
|
||||||
|
/* Default app filter, typically for when we’re instantiated by #GtkBuilder. */
|
||||||
|
if (self->app_filter == NULL)
|
||||||
|
{
|
||||||
|
g_auto(MctAppFilterBuilder) builder = MCT_APP_FILTER_BUILDER_INIT ();
|
||||||
|
self->app_filter = mct_app_filter_builder_end (&builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_assert (self->app_filter != NULL);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (mct_restrict_applications_selector_parent_class)->constructed (obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_selector_get_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
MctRestrictApplicationsSelector *self = MCT_RESTRICT_APPLICATIONS_SELECTOR (object);
|
||||||
|
|
||||||
|
switch ((MctRestrictApplicationsSelectorProperty) prop_id)
|
||||||
|
{
|
||||||
|
case PROP_APP_FILTER:
|
||||||
|
g_value_set_boxed (value, self->app_filter);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_selector_set_property (GObject *object,
|
||||||
|
guint prop_id,
|
||||||
|
const GValue *value,
|
||||||
|
GParamSpec *pspec)
|
||||||
|
{
|
||||||
|
MctRestrictApplicationsSelector *self = MCT_RESTRICT_APPLICATIONS_SELECTOR (object);
|
||||||
|
|
||||||
|
switch ((MctRestrictApplicationsSelectorProperty) prop_id)
|
||||||
|
{
|
||||||
|
case PROP_APP_FILTER:
|
||||||
|
mct_restrict_applications_selector_set_app_filter (self, g_value_get_boxed (value));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_selector_dispose (GObject *object)
|
||||||
|
{
|
||||||
|
MctRestrictApplicationsSelector *self = (MctRestrictApplicationsSelector *)object;
|
||||||
|
|
||||||
|
g_clear_pointer (&self->blacklisted_apps, g_hash_table_unref);
|
||||||
|
g_clear_object (&self->apps);
|
||||||
|
|
||||||
|
if (self->app_info_monitor != NULL && self->app_info_monitor_changed_id != 0)
|
||||||
|
{
|
||||||
|
g_signal_handler_disconnect (self->app_info_monitor, self->app_info_monitor_changed_id);
|
||||||
|
self->app_info_monitor_changed_id = 0;
|
||||||
|
}
|
||||||
|
g_clear_object (&self->app_info_monitor);
|
||||||
|
g_clear_pointer (&self->app_filter, mct_app_filter_unref);
|
||||||
|
g_clear_object (&self->system_installation);
|
||||||
|
g_clear_object (&self->user_installation);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (mct_restrict_applications_selector_parent_class)->dispose (object);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_selector_class_init (MctRestrictApplicationsSelectorClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->constructed = mct_restrict_applications_selector_constructed;
|
||||||
|
object_class->get_property = mct_restrict_applications_selector_get_property;
|
||||||
|
object_class->set_property = mct_restrict_applications_selector_set_property;
|
||||||
|
object_class->dispose = mct_restrict_applications_selector_dispose;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MctRestrictApplicationsSelector:app-filter: (not nullable)
|
||||||
|
*
|
||||||
|
* The user’s current app filter, used to set up the selector. As app filters
|
||||||
|
* are immutable, it is not updated as the selector is changed. Use
|
||||||
|
* mct_restrict_applications_selector_build_app_filter() to build the new app
|
||||||
|
* filter.
|
||||||
|
*
|
||||||
|
* 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 selector.",
|
||||||
|
MCT_TYPE_APP_FILTER,
|
||||||
|
G_PARAM_READWRITE |
|
||||||
|
G_PARAM_STATIC_STRINGS |
|
||||||
|
G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MctRestrictApplicationsSelector::changed:
|
||||||
|
*
|
||||||
|
* Emitted whenever an application in the list is blocked or unblocked.
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
signals[SIGNAL_CHANGED] =
|
||||||
|
g_signal_new ("changed",
|
||||||
|
MCT_TYPE_RESTRICT_APPLICATIONS_SELECTOR,
|
||||||
|
G_SIGNAL_RUN_LAST,
|
||||||
|
0,
|
||||||
|
NULL, NULL,
|
||||||
|
g_cclosure_marshal_VOID__VOID,
|
||||||
|
G_TYPE_NONE, 0);
|
||||||
|
|
||||||
|
gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/restrict-applications-selector.ui");
|
||||||
|
|
||||||
|
gtk_widget_class_bind_template_child (widget_class, MctRestrictApplicationsSelector, listbox);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
mct_restrict_applications_selector_init (MctRestrictApplicationsSelector *self)
|
||||||
|
{
|
||||||
|
gtk_widget_init_template (GTK_WIDGET (self));
|
||||||
|
|
||||||
|
self->apps = g_list_store_new (G_TYPE_APP_INFO);
|
||||||
|
|
||||||
|
self->app_info_monitor = g_app_info_monitor_get ();
|
||||||
|
self->app_info_monitor_changed_id =
|
||||||
|
g_signal_connect (self->app_info_monitor, "changed",
|
||||||
|
(GCallback) app_info_changed_cb, self);
|
||||||
|
|
||||||
|
gtk_list_box_bind_model (self->listbox,
|
||||||
|
G_LIST_MODEL (self->apps),
|
||||||
|
create_row_for_app_cb,
|
||||||
|
self,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
self->blacklisted_apps = g_hash_table_new_full (g_direct_hash,
|
||||||
|
g_direct_equal,
|
||||||
|
g_object_unref,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
self->system_installation = flatpak_installation_new_system (NULL, NULL);
|
||||||
|
self->user_installation = flatpak_installation_new_user (NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_switch_active_changed_cb (GtkSwitch *s,
|
||||||
|
GParamSpec *pspec,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
MctRestrictApplicationsSelector *self = MCT_RESTRICT_APPLICATIONS_SELECTOR (user_data);
|
||||||
|
GAppInfo *app;
|
||||||
|
gboolean allowed;
|
||||||
|
|
||||||
|
app = g_object_get_data (G_OBJECT (s), "GAppInfo");
|
||||||
|
allowed = gtk_switch_get_active (s);
|
||||||
|
|
||||||
|
if (allowed)
|
||||||
|
{
|
||||||
|
gboolean removed;
|
||||||
|
|
||||||
|
g_debug ("Removing ‘%s’ from blacklisted apps", g_app_info_get_id (app));
|
||||||
|
|
||||||
|
removed = g_hash_table_remove (self->blacklisted_apps, app);
|
||||||
|
g_assert (removed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gboolean added;
|
||||||
|
|
||||||
|
g_debug ("Blacklisting ‘%s’", g_app_info_get_id (app));
|
||||||
|
|
||||||
|
added = g_hash_table_add (self->blacklisted_apps, g_object_ref (app));
|
||||||
|
g_assert (added);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_signal_emit (self, signals[SIGNAL_CHANGED], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkWidget *
|
||||||
|
create_row_for_app_cb (gpointer item,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
MctRestrictApplicationsSelector *self = MCT_RESTRICT_APPLICATIONS_SELECTOR (user_data);
|
||||||
|
GAppInfo *app = G_APP_INFO (item);
|
||||||
|
g_autoptr(GIcon) icon = NULL;
|
||||||
|
GtkWidget *box, *w;
|
||||||
|
gboolean allowed;
|
||||||
|
const gchar *app_name;
|
||||||
|
gint size;
|
||||||
|
|
||||||
|
app_name = g_app_info_get_name (app);
|
||||||
|
|
||||||
|
g_assert (G_IS_DESKTOP_APP_INFO (app));
|
||||||
|
|
||||||
|
icon = g_app_info_get_icon (app);
|
||||||
|
if (icon == NULL)
|
||||||
|
icon = g_themed_icon_new ("application-x-executable");
|
||||||
|
else
|
||||||
|
g_object_ref (icon);
|
||||||
|
|
||||||
|
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
|
||||||
|
gtk_container_set_border_width (GTK_CONTAINER (box), 12);
|
||||||
|
gtk_widget_set_margin_end (box, 12);
|
||||||
|
|
||||||
|
/* Icon */
|
||||||
|
w = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
|
||||||
|
gtk_icon_size_lookup (GTK_ICON_SIZE_DND, &size, NULL);
|
||||||
|
gtk_image_set_pixel_size (GTK_IMAGE (w), size);
|
||||||
|
gtk_container_add (GTK_CONTAINER (box), w);
|
||||||
|
|
||||||
|
/* App name label */
|
||||||
|
w = g_object_new (GTK_TYPE_LABEL,
|
||||||
|
"label", app_name,
|
||||||
|
"hexpand", TRUE,
|
||||||
|
"xalign", 0.0,
|
||||||
|
NULL);
|
||||||
|
gtk_container_add (GTK_CONTAINER (box), w);
|
||||||
|
|
||||||
|
/* Switch */
|
||||||
|
w = g_object_new (GTK_TYPE_SWITCH,
|
||||||
|
"valign", GTK_ALIGN_CENTER,
|
||||||
|
NULL);
|
||||||
|
gtk_container_add (GTK_CONTAINER (box), w);
|
||||||
|
|
||||||
|
gtk_widget_show_all (box);
|
||||||
|
|
||||||
|
/* Fetch status from AccountService */
|
||||||
|
allowed = mct_app_filter_is_appinfo_allowed (self->app_filter, app);
|
||||||
|
|
||||||
|
gtk_switch_set_active (GTK_SWITCH (w), allowed);
|
||||||
|
g_object_set_data_full (G_OBJECT (w), "GAppInfo", g_object_ref (app), g_object_unref);
|
||||||
|
|
||||||
|
if (allowed)
|
||||||
|
g_hash_table_remove (self->blacklisted_apps, app);
|
||||||
|
else
|
||||||
|
g_hash_table_add (self->blacklisted_apps, g_object_ref (app));
|
||||||
|
|
||||||
|
g_signal_connect (w, "notify::active", G_CALLBACK (on_switch_active_changed_cb), self);
|
||||||
|
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gint
|
||||||
|
compare_app_info_cb (gconstpointer a,
|
||||||
|
gconstpointer b,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
GAppInfo *app_a = (GAppInfo*) a;
|
||||||
|
GAppInfo *app_b = (GAppInfo*) b;
|
||||||
|
|
||||||
|
return g_utf8_collate (g_app_info_get_display_name (app_a),
|
||||||
|
g_app_info_get_display_name (app_b));
|
||||||
|
}
|
||||||
|
|
||||||
|
static gint
|
||||||
|
app_compare_id_length_cb (gconstpointer a,
|
||||||
|
gconstpointer b)
|
||||||
|
{
|
||||||
|
GAppInfo *info_a = (GAppInfo *) a, *info_b = (GAppInfo *) b;
|
||||||
|
const gchar *id_a, *id_b;
|
||||||
|
|
||||||
|
id_a = g_app_info_get_id (info_a);
|
||||||
|
id_b = g_app_info_get_id (info_b);
|
||||||
|
|
||||||
|
if (id_a == NULL && id_b == NULL)
|
||||||
|
return 0;
|
||||||
|
else if (id_a == NULL)
|
||||||
|
return -1;
|
||||||
|
else if (id_b == NULL)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return strlen (id_a) - strlen (id_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
reload_apps (MctRestrictApplicationsSelector *self)
|
||||||
|
{
|
||||||
|
GList *iter, *apps;
|
||||||
|
g_autoptr(GHashTable) seen_flatpak_ids = NULL;
|
||||||
|
g_autoptr(GHashTable) seen_executables = NULL;
|
||||||
|
|
||||||
|
apps = g_app_info_get_all ();
|
||||||
|
|
||||||
|
/* Sort the apps by increasing length of #GAppInfo ID. When coupled with the
|
||||||
|
* deduplication of flatpak IDs and executable paths, below, this should ensure that we
|
||||||
|
* pick the ‘base’ app out of any set with matching prefixes and identical app IDs (in
|
||||||
|
* case of flatpak apps) or executables (for non-flatpak apps), and show only that.
|
||||||
|
*
|
||||||
|
* This is designed to avoid listing all the components of LibreOffice for example,
|
||||||
|
* which all share an app ID and hence have the same entry in the parental controls
|
||||||
|
* app filter. */
|
||||||
|
apps = g_list_sort (apps, app_compare_id_length_cb);
|
||||||
|
seen_flatpak_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||||
|
seen_executables = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||||
|
|
||||||
|
g_list_store_remove_all (self->apps);
|
||||||
|
|
||||||
|
for (iter = apps; iter; iter = iter->next)
|
||||||
|
{
|
||||||
|
GAppInfo *app;
|
||||||
|
const gchar *app_name;
|
||||||
|
const gchar * const *supported_types;
|
||||||
|
|
||||||
|
app = iter->data;
|
||||||
|
app_name = g_app_info_get_name (app);
|
||||||
|
|
||||||
|
supported_types = g_app_info_get_supported_types (app);
|
||||||
|
|
||||||
|
if (!G_IS_DESKTOP_APP_INFO (app) ||
|
||||||
|
!g_app_info_should_show (app) ||
|
||||||
|
app_name[0] == '\0' ||
|
||||||
|
/* Endless' link apps have the "eos-link" prefix, and should be ignored too */
|
||||||
|
g_str_has_prefix (g_app_info_get_id (app), "eos-link") ||
|
||||||
|
/* FIXME: Only list flatpak apps and apps with X-Parental-Controls
|
||||||
|
* key set for now; we really need a system-wide MAC to be able to
|
||||||
|
* reliably support blacklisting system programs. */
|
||||||
|
(!g_desktop_app_info_has_key (G_DESKTOP_APP_INFO (app), "X-Flatpak") &&
|
||||||
|
!g_desktop_app_info_has_key (G_DESKTOP_APP_INFO (app), "X-Parental-Controls")) ||
|
||||||
|
/* Web browsers are special cased */
|
||||||
|
(supported_types && g_strv_contains (supported_types, WEB_BROWSERS_CONTENT_TYPE)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g_desktop_app_info_has_key (G_DESKTOP_APP_INFO (app), "X-Flatpak"))
|
||||||
|
{
|
||||||
|
g_autofree gchar *flatpak_id = NULL;
|
||||||
|
|
||||||
|
flatpak_id = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (app), "X-Flatpak");
|
||||||
|
g_debug ("Processing app ‘%s’ (Exec=%s, X-Flatpak=%s)",
|
||||||
|
g_app_info_get_id (app),
|
||||||
|
g_app_info_get_executable (app),
|
||||||
|
flatpak_id);
|
||||||
|
|
||||||
|
/* Have we seen this flatpak ID before? */
|
||||||
|
if (!g_hash_table_add (seen_flatpak_ids, g_steal_pointer (&flatpak_id)))
|
||||||
|
{
|
||||||
|
g_debug (" → Skipping ‘%s’ due to seeing its flatpak ID already",
|
||||||
|
g_app_info_get_id (app));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (g_desktop_app_info_has_key (G_DESKTOP_APP_INFO (app), "X-Parental-Controls"))
|
||||||
|
{
|
||||||
|
g_autofree gchar *parental_controls_type = NULL;
|
||||||
|
g_autofree gchar *executable = NULL;
|
||||||
|
|
||||||
|
parental_controls_type = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (app),
|
||||||
|
"X-Parental-Controls");
|
||||||
|
/* Ignore X-Parental-Controls=none */
|
||||||
|
if (g_strcmp0 (parental_controls_type, "none") == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
executable = g_strdup (g_app_info_get_executable (app));
|
||||||
|
g_debug ("Processing app ‘%s’ (Exec=%s, X-Parental-Controls=%s)",
|
||||||
|
g_app_info_get_id (app),
|
||||||
|
executable,
|
||||||
|
parental_controls_type);
|
||||||
|
|
||||||
|
/* Have we seen this executable before? */
|
||||||
|
if (!g_hash_table_add (seen_executables, g_steal_pointer (&executable)))
|
||||||
|
{
|
||||||
|
g_debug (" → Skipping ‘%s’ due to seeing its executable already",
|
||||||
|
g_app_info_get_id (app));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_list_store_insert_sorted (self->apps,
|
||||||
|
app,
|
||||||
|
compare_app_info_cb,
|
||||||
|
self);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_list_free_full (apps, g_object_unref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
app_info_changed_cb (GAppInfoMonitor *monitor,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
/* FIXME: We should update the list of apps here, but we can’t call
|
||||||
|
* reload_apps() because that will dump and reload the entire list, losing
|
||||||
|
* any changes the user has already made to the set of switches. We need
|
||||||
|
* something more fine-grained.
|
||||||
|
MctRestrictApplicationsSelector *self = MCT_RESTRICT_APPLICATIONS_SELECTOR (user_data);
|
||||||
|
|
||||||
|
reload_apps (self);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Will return %NULL if @flatpak_id is not installed. */
|
||||||
|
static gchar *
|
||||||
|
get_flatpak_ref_for_app_id (MctRestrictApplicationsSelector *self,
|
||||||
|
const gchar *flatpak_id,
|
||||||
|
GCancellable *cancellable)
|
||||||
|
{
|
||||||
|
g_autoptr(FlatpakInstalledRef) ref = NULL;
|
||||||
|
g_autoptr(GError) local_error = NULL;
|
||||||
|
|
||||||
|
g_assert (self->system_installation != NULL);
|
||||||
|
g_assert (self->user_installation != NULL);
|
||||||
|
|
||||||
|
/* FIXME technically this does local file I/O and should be async */
|
||||||
|
ref = flatpak_installation_get_current_installed_app (self->user_installation,
|
||||||
|
flatpak_id,
|
||||||
|
cancellable,
|
||||||
|
&local_error);
|
||||||
|
|
||||||
|
if (local_error != NULL &&
|
||||||
|
!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
|
||||||
|
{
|
||||||
|
g_warning ("Error searching for Flatpak ref: %s", local_error->message);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_clear_error (&local_error);
|
||||||
|
|
||||||
|
if (!ref || !flatpak_installed_ref_get_is_current (ref))
|
||||||
|
{
|
||||||
|
/* FIXME technically this does local file I/O and should be async */
|
||||||
|
ref = flatpak_installation_get_current_installed_app (self->system_installation,
|
||||||
|
flatpak_id,
|
||||||
|
cancellable,
|
||||||
|
&local_error);
|
||||||
|
if (local_error != NULL)
|
||||||
|
{
|
||||||
|
if (!g_error_matches (local_error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
|
||||||
|
g_warning ("Error searching for Flatpak ref: %s", local_error->message);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return flatpak_ref_format_ref (FLATPAK_REF (ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_selector_new:
|
||||||
|
* @app_filter: (transfer none): app filter to configure the selector from initially
|
||||||
|
*
|
||||||
|
* Create a new #MctRestrictApplicationsSelector widget.
|
||||||
|
*
|
||||||
|
* Returns: (transfer full): a new restricted applications selector
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
MctRestrictApplicationsSelector *
|
||||||
|
mct_restrict_applications_selector_new (MctAppFilter *app_filter)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (app_filter != NULL, NULL);
|
||||||
|
|
||||||
|
return g_object_new (MCT_TYPE_RESTRICT_APPLICATIONS_SELECTOR,
|
||||||
|
"app-filter", app_filter,
|
||||||
|
NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_selector_build_app_filter:
|
||||||
|
* @self: an #MctRestrictApplicationsSelector
|
||||||
|
* @builder: an existing #MctAppFilterBuilder to modify
|
||||||
|
*
|
||||||
|
* Get the app filter settings currently configured in the selector, by modifying
|
||||||
|
* the given @builder.
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
mct_restrict_applications_selector_build_app_filter (MctRestrictApplicationsSelector *self,
|
||||||
|
MctAppFilterBuilder *builder)
|
||||||
|
{
|
||||||
|
GDesktopAppInfo *app;
|
||||||
|
GHashTableIter iter;
|
||||||
|
|
||||||
|
g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_SELECTOR (self));
|
||||||
|
g_return_if_fail (builder != NULL);
|
||||||
|
|
||||||
|
g_hash_table_iter_init (&iter, self->blacklisted_apps);
|
||||||
|
while (g_hash_table_iter_next (&iter, (gpointer) &app, NULL))
|
||||||
|
{
|
||||||
|
g_autofree gchar *flatpak_id = NULL;
|
||||||
|
|
||||||
|
flatpak_id = g_desktop_app_info_get_string (app, "X-Flatpak");
|
||||||
|
if (flatpak_id)
|
||||||
|
flatpak_id = g_strstrip (flatpak_id);
|
||||||
|
|
||||||
|
if (flatpak_id)
|
||||||
|
{
|
||||||
|
g_autofree gchar *flatpak_ref = get_flatpak_ref_for_app_id (self, flatpak_id, NULL);
|
||||||
|
|
||||||
|
if (!flatpak_ref)
|
||||||
|
{
|
||||||
|
g_warning ("Skipping blacklisting Flatpak ID ‘%s’ due to it not being installed", flatpak_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug ("\t\t → Blacklisting Flatpak ref: %s", flatpak_ref);
|
||||||
|
mct_app_filter_builder_blacklist_flatpak_ref (builder, flatpak_ref);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const gchar *executable = g_app_info_get_executable (G_APP_INFO (app));
|
||||||
|
g_autofree gchar *path = g_find_program_in_path (executable);
|
||||||
|
|
||||||
|
if (!path)
|
||||||
|
{
|
||||||
|
g_warning ("Skipping blacklisting executable ‘%s’ due to it not being found", executable);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_debug ("\t\t → Blacklisting path: %s", path);
|
||||||
|
mct_app_filter_builder_blacklist_path (builder, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_selector_get_app_filter:
|
||||||
|
* @self: an #MctRestrictApplicationsSelector
|
||||||
|
*
|
||||||
|
* Get the value of #MctRestrictApplicationsSelector:app-filter. If the property
|
||||||
|
* was originally set to %NULL, this will be the empty app filter.
|
||||||
|
*
|
||||||
|
* Returns: (transfer none) (not nullable): the initial app filter used to
|
||||||
|
* populate the selector
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
MctAppFilter *
|
||||||
|
mct_restrict_applications_selector_get_app_filter (MctRestrictApplicationsSelector *self)
|
||||||
|
{
|
||||||
|
g_return_val_if_fail (MCT_IS_RESTRICT_APPLICATIONS_SELECTOR (self), NULL);
|
||||||
|
|
||||||
|
return self->app_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mct_restrict_applications_selector_set_app_filter:
|
||||||
|
* @self: an #MctRestrictApplicationsSelector
|
||||||
|
* @app_filter: (nullable) (transfer none): the app filter to configure the selector
|
||||||
|
* from, or %NULL to use an empty app filter
|
||||||
|
*
|
||||||
|
* Set the value of #MctRestrictApplicationsSelector:app-filter.
|
||||||
|
*
|
||||||
|
* This will overwrite any user changes to the selector, so they should be saved
|
||||||
|
* first using mct_restrict_applications_selector_build_app_filter() if desired.
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
mct_restrict_applications_selector_set_app_filter (MctRestrictApplicationsSelector *self,
|
||||||
|
MctAppFilter *app_filter)
|
||||||
|
{
|
||||||
|
g_autoptr(MctAppFilter) owned_app_filter = NULL;
|
||||||
|
|
||||||
|
g_return_if_fail (MCT_IS_RESTRICT_APPLICATIONS_SELECTOR (self));
|
||||||
|
|
||||||
|
/* Default app filter, typically for when we’re instantiated by #GtkBuilder. */
|
||||||
|
if (app_filter == NULL)
|
||||||
|
{
|
||||||
|
g_auto(MctAppFilterBuilder) builder = MCT_APP_FILTER_BUILDER_INIT ();
|
||||||
|
owned_app_filter = mct_app_filter_builder_end (&builder);
|
||||||
|
app_filter = owned_app_filter;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app_filter == self->app_filter)
|
||||||
|
return;
|
||||||
|
|
||||||
|
g_clear_pointer (&self->app_filter, mct_app_filter_unref);
|
||||||
|
self->app_filter = mct_app_filter_ref (app_filter);
|
||||||
|
|
||||||
|
reload_apps (self);
|
||||||
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_APP_FILTER]);
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
|
||||||
|
*
|
||||||
|
* Copyright © 2020 Endless Mobile, 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, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* Authors:
|
||||||
|
* - Philip Withnall <withnall@endlessm.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <act/act.h>
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib-object.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <libmalcontent/app-filter.h>
|
||||||
|
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define MCT_TYPE_RESTRICT_APPLICATIONS_SELECTOR (mct_restrict_applications_selector_get_type ())
|
||||||
|
G_DECLARE_FINAL_TYPE (MctRestrictApplicationsSelector, mct_restrict_applications_selector, MCT, RESTRICT_APPLICATIONS_SELECTOR, GtkBox)
|
||||||
|
|
||||||
|
MctRestrictApplicationsSelector *mct_restrict_applications_selector_new (MctAppFilter *app_filter);
|
||||||
|
|
||||||
|
MctAppFilter *mct_restrict_applications_selector_get_app_filter (MctRestrictApplicationsSelector *self);
|
||||||
|
void mct_restrict_applications_selector_set_app_filter (MctRestrictApplicationsSelector *self,
|
||||||
|
MctAppFilter *app_filter);
|
||||||
|
|
||||||
|
void mct_restrict_applications_selector_build_app_filter (MctRestrictApplicationsSelector *self,
|
||||||
|
MctAppFilterBuilder *builder);
|
||||||
|
|
||||||
|
G_END_DECLS
|
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Copyright © 2020 Endless, Inc. -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.12"/>
|
||||||
|
<template class="MctRestrictApplicationsSelector" parent="GtkBox">
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="hscrollbar-policy">never</property>
|
||||||
|
<property name="min-content-height">100</property>
|
||||||
|
<property name="max-content-height">400</property>
|
||||||
|
<property name="propagate-natural-height">True</property>
|
||||||
|
<property name="shadow-type">etched-in</property>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBox" id="listbox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="selection-mode">none</property>
|
||||||
|
|
||||||
|
<child type="placeholder">
|
||||||
|
<object class="GtkLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="label" translatable="yes">No applications found to restrict.</property>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</template>
|
||||||
|
</interface>
|
|
@ -28,6 +28,7 @@
|
||||||
#include <strings.h>
|
#include <strings.h>
|
||||||
|
|
||||||
#include "gs-content-rating.h"
|
#include "gs-content-rating.h"
|
||||||
|
#include "restrict-applications-dialog.h"
|
||||||
#include "user-controls.h"
|
#include "user-controls.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -44,12 +45,9 @@ struct _MctUserControls
|
||||||
GtkSwitch *allow_system_installation_switch;
|
GtkSwitch *allow_system_installation_switch;
|
||||||
GtkSwitch *allow_user_installation_switch;
|
GtkSwitch *allow_user_installation_switch;
|
||||||
GtkSwitch *allow_web_browsers_switch;
|
GtkSwitch *allow_web_browsers_switch;
|
||||||
GtkListBox *listbox;
|
|
||||||
GtkButton *restriction_button;
|
GtkButton *restriction_button;
|
||||||
GtkPopover *restriction_popover;
|
GtkPopover *restriction_popover;
|
||||||
|
MctRestrictApplicationsDialog *restrict_applications_dialog;
|
||||||
FlatpakInstallation *system_installation; /* (owned) */
|
|
||||||
FlatpakInstallation *user_installation; /* (owned) */
|
|
||||||
|
|
||||||
GSimpleActionGroup *action_group; /* (owned) */
|
GSimpleActionGroup *action_group; /* (owned) */
|
||||||
|
|
||||||
|
@ -58,26 +56,16 @@ struct _MctUserControls
|
||||||
GPermission *permission; /* (owned) (nullable) */
|
GPermission *permission; /* (owned) (nullable) */
|
||||||
gulong permission_allowed_id;
|
gulong permission_allowed_id;
|
||||||
|
|
||||||
GAppInfoMonitor *app_info_monitor; /* (owned) */
|
|
||||||
|
|
||||||
GHashTable *blacklisted_apps; /* (owned) */
|
|
||||||
GListStore *apps; /* (owned) */
|
|
||||||
|
|
||||||
GCancellable *cancellable; /* (owned) */
|
GCancellable *cancellable; /* (owned) */
|
||||||
MctManager *manager; /* (owned) */
|
MctManager *manager; /* (owned) */
|
||||||
MctAppFilter *filter; /* (owned) */
|
MctAppFilter *filter; /* (owned) */
|
||||||
guint selected_age; /* @oars_disabled_age to disable OARS */
|
guint selected_age; /* @oars_disabled_age to disable OARS */
|
||||||
|
|
||||||
guint blacklist_apps_source_id;
|
guint blacklist_apps_source_id;
|
||||||
|
gboolean flushed_on_dispose;
|
||||||
};
|
};
|
||||||
|
|
||||||
static gboolean blacklist_apps_cb (gpointer data);
|
static gboolean blacklist_apps_cb (gpointer data);
|
||||||
static void app_info_changed_cb (GAppInfoMonitor *monitor,
|
|
||||||
gpointer user_data);
|
|
||||||
|
|
||||||
static gint compare_app_info_cb (gconstpointer a,
|
|
||||||
gconstpointer b,
|
|
||||||
gpointer user_data);
|
|
||||||
|
|
||||||
static void on_allow_installation_switch_active_changed_cb (GtkSwitch *s,
|
static void on_allow_installation_switch_active_changed_cb (GtkSwitch *s,
|
||||||
GParamSpec *pspec,
|
GParamSpec *pspec,
|
||||||
|
@ -87,6 +75,17 @@ static void on_allow_web_browsers_switch_active_changed_cb (GtkSwitch *s,
|
||||||
GParamSpec *pspec,
|
GParamSpec *pspec,
|
||||||
MctUserControls *self);
|
MctUserControls *self);
|
||||||
|
|
||||||
|
static void on_restrict_applications_button_clicked_cb (GtkButton *button,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
|
static gboolean on_restrict_applications_dialog_delete_event_cb (GtkWidget *widget,
|
||||||
|
GdkEvent *event,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
|
static void on_restrict_applications_dialog_response_cb (GtkDialog *dialog,
|
||||||
|
gint response_id,
|
||||||
|
gpointer user_data);
|
||||||
|
|
||||||
static void on_set_age_action_activated (GSimpleAction *action,
|
static void on_set_age_action_activated (GSimpleAction *action,
|
||||||
GVariant *param,
|
GVariant *param,
|
||||||
gpointer user_data);
|
gpointer user_data);
|
||||||
|
@ -111,7 +110,7 @@ static const GActionEntry actions[] = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* FIXME: Factor this out and rely on code from libappstream-glib or gnome-software
|
/* FIXME: Factor this out and rely on code from libappstream-glib or gnome-software
|
||||||
* to do it. See: https://phabricator.endlessm.com/T24986 */
|
* to do it. See: https://gitlab.freedesktop.org/pwithnall/malcontent/issues/7 */
|
||||||
static const gchar * const oars_categories[] =
|
static const gchar * const oars_categories[] =
|
||||||
{
|
{
|
||||||
"violence-cartoon",
|
"violence-cartoon",
|
||||||
|
@ -146,139 +145,6 @@ static const gchar * const oars_categories[] =
|
||||||
|
|
||||||
/* Auxiliary methods */
|
/* Auxiliary methods */
|
||||||
|
|
||||||
static gint
|
|
||||||
app_compare_id_length_cb (gconstpointer a,
|
|
||||||
gconstpointer b)
|
|
||||||
{
|
|
||||||
GAppInfo *info_a = (GAppInfo *) a, *info_b = (GAppInfo *) b;
|
|
||||||
const gchar *id_a, *id_b;
|
|
||||||
|
|
||||||
id_a = g_app_info_get_id (info_a);
|
|
||||||
id_b = g_app_info_get_id (info_b);
|
|
||||||
|
|
||||||
if (id_a == NULL && id_b == NULL)
|
|
||||||
return 0;
|
|
||||||
else if (id_a == NULL)
|
|
||||||
return -1;
|
|
||||||
else if (id_b == NULL)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return strlen (id_a) - strlen (id_b);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
reload_apps (MctUserControls *self)
|
|
||||||
{
|
|
||||||
GList *iter, *apps;
|
|
||||||
g_autoptr(GHashTable) seen_flatpak_ids = NULL;
|
|
||||||
g_autoptr(GHashTable) seen_executables = NULL;
|
|
||||||
|
|
||||||
apps = g_app_info_get_all ();
|
|
||||||
|
|
||||||
/* Sort the apps by increasing length of #GAppInfo ID. When coupled with the
|
|
||||||
* deduplication of flatpak IDs and executable paths, below, this should ensure that we
|
|
||||||
* pick the ‘base’ app out of any set with matching prefixes and identical app IDs (in
|
|
||||||
* case of flatpak apps) or executables (for non-flatpak apps), and show only that.
|
|
||||||
*
|
|
||||||
* This is designed to avoid listing all the components of LibreOffice for example,
|
|
||||||
* which all share an app ID and hence have the same entry in the parental controls
|
|
||||||
* app filter. */
|
|
||||||
apps = g_list_sort (apps, app_compare_id_length_cb);
|
|
||||||
seen_flatpak_ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
||||||
seen_executables = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
|
||||||
|
|
||||||
g_list_store_remove_all (self->apps);
|
|
||||||
|
|
||||||
for (iter = apps; iter; iter = iter->next)
|
|
||||||
{
|
|
||||||
GAppInfo *app;
|
|
||||||
const gchar *app_name;
|
|
||||||
const gchar * const *supported_types;
|
|
||||||
|
|
||||||
app = iter->data;
|
|
||||||
app_name = g_app_info_get_name (app);
|
|
||||||
|
|
||||||
supported_types = g_app_info_get_supported_types (app);
|
|
||||||
|
|
||||||
if (!G_IS_DESKTOP_APP_INFO (app) ||
|
|
||||||
!g_app_info_should_show (app) ||
|
|
||||||
app_name[0] == '\0' ||
|
|
||||||
/* Endless' link apps have the "eos-link" prefix, and should be ignored too */
|
|
||||||
g_str_has_prefix (g_app_info_get_id (app), "eos-link") ||
|
|
||||||
/* FIXME: Only list flatpak apps and apps with X-Parental-Controls
|
|
||||||
* key set for now; we really need a system-wide MAC to be able to
|
|
||||||
* reliably support blacklisting system programs. See
|
|
||||||
* https://phabricator.endlessm.com/T25080. */
|
|
||||||
(!g_desktop_app_info_has_key (G_DESKTOP_APP_INFO (app), "X-Flatpak") &&
|
|
||||||
!g_desktop_app_info_has_key (G_DESKTOP_APP_INFO (app), "X-Parental-Controls")) ||
|
|
||||||
/* Web browsers are special cased */
|
|
||||||
(supported_types && g_strv_contains (supported_types, WEB_BROWSERS_CONTENT_TYPE)))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (g_desktop_app_info_has_key (G_DESKTOP_APP_INFO (app), "X-Flatpak"))
|
|
||||||
{
|
|
||||||
g_autofree gchar *flatpak_id = NULL;
|
|
||||||
|
|
||||||
flatpak_id = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (app), "X-Flatpak");
|
|
||||||
g_debug ("Processing app ‘%s’ (Exec=%s, X-Flatpak=%s)",
|
|
||||||
g_app_info_get_id (app),
|
|
||||||
g_app_info_get_executable (app),
|
|
||||||
flatpak_id);
|
|
||||||
|
|
||||||
/* Have we seen this flatpak ID before? */
|
|
||||||
if (!g_hash_table_add (seen_flatpak_ids, g_steal_pointer (&flatpak_id)))
|
|
||||||
{
|
|
||||||
g_debug (" → Skipping ‘%s’ due to seeing its flatpak ID already",
|
|
||||||
g_app_info_get_id (app));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (g_desktop_app_info_has_key (G_DESKTOP_APP_INFO (app), "X-Parental-Controls"))
|
|
||||||
{
|
|
||||||
g_autofree gchar *parental_controls_type = NULL;
|
|
||||||
g_autofree gchar *executable = NULL;
|
|
||||||
|
|
||||||
parental_controls_type = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (app),
|
|
||||||
"X-Parental-Controls");
|
|
||||||
/* Ignore X-Parental-Controls=none */
|
|
||||||
if (g_strcmp0 (parental_controls_type, "none") == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
executable = g_strdup (g_app_info_get_executable (app));
|
|
||||||
g_debug ("Processing app ‘%s’ (Exec=%s, X-Parental-Controls=%s)",
|
|
||||||
g_app_info_get_id (app),
|
|
||||||
executable,
|
|
||||||
parental_controls_type);
|
|
||||||
|
|
||||||
/* Have we seen this executable before? */
|
|
||||||
if (!g_hash_table_add (seen_executables, g_steal_pointer (&executable)))
|
|
||||||
{
|
|
||||||
g_debug (" → Skipping ‘%s’ due to seeing its executable already",
|
|
||||||
g_app_info_get_id (app));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
g_list_store_insert_sorted (self->apps,
|
|
||||||
app,
|
|
||||||
compare_app_info_cb,
|
|
||||||
self);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_list_free_full (apps, g_object_unref);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
app_info_changed_cb (GAppInfoMonitor *monitor,
|
|
||||||
gpointer user_data)
|
|
||||||
{
|
|
||||||
MctUserControls *self = MCT_USER_CONTROLS (user_data);
|
|
||||||
|
|
||||||
reload_apps (self);
|
|
||||||
}
|
|
||||||
|
|
||||||
static GsContentRatingSystem
|
static GsContentRatingSystem
|
||||||
get_content_rating_system (ActUser *user)
|
get_content_rating_system (ActUser *user)
|
||||||
{
|
{
|
||||||
|
@ -320,6 +186,18 @@ update_app_filter (MctUserControls *self)
|
||||||
|
|
||||||
g_clear_pointer (&self->filter, mct_app_filter_unref);
|
g_clear_pointer (&self->filter, mct_app_filter_unref);
|
||||||
|
|
||||||
|
if (self->user == NULL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* FIXME: It’s expected that, unless authorised already, a user cannot read
|
||||||
|
* another user’s app filter. accounts-service currently (incorrectly) ignores
|
||||||
|
* the missing ‘interactive’ flag and prompts the user for permission if so,
|
||||||
|
* so don’t query at all in that case. */
|
||||||
|
if (act_user_get_uid (self->user) != getuid () &&
|
||||||
|
(self->permission == NULL ||
|
||||||
|
!g_permission_get_allowed (self->permission)))
|
||||||
|
return;
|
||||||
|
|
||||||
/* FIXME: make it asynchronous */
|
/* FIXME: make it asynchronous */
|
||||||
self->filter = mct_manager_get_app_filter (self->manager,
|
self->filter = mct_manager_get_app_filter (self->manager,
|
||||||
act_user_get_uid (self->user),
|
act_user_get_uid (self->user),
|
||||||
|
@ -328,27 +206,10 @@ update_app_filter (MctUserControls *self)
|
||||||
&error);
|
&error);
|
||||||
|
|
||||||
if (error)
|
if (error)
|
||||||
{
|
|
||||||
/* It's expected that a non-admin user can't read another user's parental
|
|
||||||
* controls info unless the panel has been unlocked; ignore such an
|
|
||||||
* error.
|
|
||||||
*/
|
|
||||||
if (act_user_get_uid (self->user) != getuid () &&
|
|
||||||
self->permission != NULL &&
|
|
||||||
!g_permission_get_allowed (self->permission) &&
|
|
||||||
g_error_matches (error, MCT_MANAGER_ERROR, MCT_MANAGER_ERROR_PERMISSION_DENIED))
|
|
||||||
{
|
|
||||||
g_clear_error (&error);
|
|
||||||
g_debug ("Not enough permissions to retrieve app filter for user '%s'",
|
|
||||||
act_user_get_user_name (self->user));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
g_warning ("Error retrieving app filter for user '%s': %s",
|
g_warning ("Error retrieving app filter for user '%s': %s",
|
||||||
act_user_get_user_name (self->user),
|
act_user_get_user_name (self->user),
|
||||||
error->message);
|
error->message);
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,7 +238,7 @@ update_categories_from_language (MctUserControls *self)
|
||||||
g_menu_remove_all (self->age_menu);
|
g_menu_remove_all (self->age_menu);
|
||||||
|
|
||||||
disabled_action = g_strdup_printf ("permissions.set-age(uint32 %u)", oars_disabled_age);
|
disabled_action = g_strdup_printf ("permissions.set-age(uint32 %u)", oars_disabled_age);
|
||||||
g_menu_append (self->age_menu, _("No Restriction"), disabled_action);
|
g_menu_append (self->age_menu, _("All Ages"), disabled_action);
|
||||||
|
|
||||||
for (i = 0; entries[i] != NULL; i++)
|
for (i = 0; entries[i] != NULL; i++)
|
||||||
{
|
{
|
||||||
|
@ -450,7 +311,7 @@ update_oars_level (MctUserControls *self)
|
||||||
|
|
||||||
/* Unrestricted? */
|
/* Unrestricted? */
|
||||||
if (rating_age_category == NULL || all_categories_unset)
|
if (rating_age_category == NULL || all_categories_unset)
|
||||||
rating_age_category = _("No Restriction");
|
rating_age_category = _("All Ages");
|
||||||
|
|
||||||
gtk_button_set_label (self->restriction_button, rating_age_category);
|
gtk_button_set_label (self->restriction_button, rating_age_category);
|
||||||
}
|
}
|
||||||
|
@ -547,59 +408,14 @@ setup_parental_control_settings (MctUserControls *self)
|
||||||
if (self->permission != NULL)
|
if (self->permission != NULL)
|
||||||
is_authorized = g_permission_get_allowed (G_PERMISSION (self->permission));
|
is_authorized = g_permission_get_allowed (G_PERMISSION (self->permission));
|
||||||
else
|
else
|
||||||
is_authorized = TRUE;
|
is_authorized = FALSE;
|
||||||
|
|
||||||
gtk_widget_set_sensitive (GTK_WIDGET (self), is_authorized);
|
gtk_widget_set_sensitive (GTK_WIDGET (self), is_authorized);
|
||||||
|
|
||||||
g_hash_table_remove_all (self->blacklisted_apps);
|
|
||||||
|
|
||||||
update_oars_level (self);
|
update_oars_level (self);
|
||||||
update_categories_from_language (self);
|
update_categories_from_language (self);
|
||||||
update_allow_app_installation (self);
|
update_allow_app_installation (self);
|
||||||
update_allow_web_browsers (self);
|
update_allow_web_browsers (self);
|
||||||
reload_apps (self);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Will return %NULL if @flatpak_id is not installed. */
|
|
||||||
static gchar *
|
|
||||||
get_flatpak_ref_for_app_id (MctUserControls *self,
|
|
||||||
const gchar *flatpak_id)
|
|
||||||
{
|
|
||||||
g_autoptr(FlatpakInstalledRef) ref = NULL;
|
|
||||||
g_autoptr(GError) error = NULL;
|
|
||||||
|
|
||||||
g_assert (self->system_installation != NULL);
|
|
||||||
g_assert (self->user_installation != NULL);
|
|
||||||
|
|
||||||
ref = flatpak_installation_get_current_installed_app (self->user_installation,
|
|
||||||
flatpak_id,
|
|
||||||
self->cancellable,
|
|
||||||
&error);
|
|
||||||
|
|
||||||
if (error &&
|
|
||||||
!g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
|
|
||||||
{
|
|
||||||
g_warning ("Error searching for Flatpak ref: %s", error->message);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_clear_error (&error);
|
|
||||||
|
|
||||||
if (!ref || !flatpak_installed_ref_get_is_current (ref))
|
|
||||||
{
|
|
||||||
ref = flatpak_installation_get_current_installed_app (self->system_installation,
|
|
||||||
flatpak_id,
|
|
||||||
self->cancellable,
|
|
||||||
&error);
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
if (!g_error_matches (error, FLATPAK_ERROR, FLATPAK_ERROR_NOT_INSTALLED))
|
|
||||||
g_warning ("Error searching for Flatpak ref: %s", error->message);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return flatpak_ref_format_ref (FLATPAK_REF (ref));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Callbacks */
|
/* Callbacks */
|
||||||
|
@ -611,8 +427,6 @@ blacklist_apps_cb (gpointer data)
|
||||||
g_autoptr(MctAppFilter) new_filter = NULL;
|
g_autoptr(MctAppFilter) new_filter = NULL;
|
||||||
g_autoptr(GError) error = NULL;
|
g_autoptr(GError) error = NULL;
|
||||||
MctUserControls *self = data;
|
MctUserControls *self = data;
|
||||||
GDesktopAppInfo *app;
|
|
||||||
GHashTableIter iter;
|
|
||||||
gboolean allow_web_browsers;
|
gboolean allow_web_browsers;
|
||||||
gsize i;
|
gsize i;
|
||||||
|
|
||||||
|
@ -624,43 +438,7 @@ blacklist_apps_cb (gpointer data)
|
||||||
|
|
||||||
g_debug ("\t → Blacklisting apps");
|
g_debug ("\t → Blacklisting apps");
|
||||||
|
|
||||||
g_hash_table_iter_init (&iter, self->blacklisted_apps);
|
mct_restrict_applications_dialog_build_app_filter (self->restrict_applications_dialog, &builder);
|
||||||
while (g_hash_table_iter_next (&iter, (gpointer) &app, NULL))
|
|
||||||
{
|
|
||||||
g_autofree gchar *flatpak_id = NULL;
|
|
||||||
|
|
||||||
flatpak_id = g_desktop_app_info_get_string (app, "X-Flatpak");
|
|
||||||
if (flatpak_id)
|
|
||||||
flatpak_id = g_strstrip (flatpak_id);
|
|
||||||
|
|
||||||
if (flatpak_id)
|
|
||||||
{
|
|
||||||
g_autofree gchar *flatpak_ref = get_flatpak_ref_for_app_id (self, flatpak_id);
|
|
||||||
|
|
||||||
if (!flatpak_ref)
|
|
||||||
{
|
|
||||||
g_warning ("Skipping blacklisting Flatpak ID ‘%s’ due to it not being installed", flatpak_id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_debug ("\t\t → Blacklisting Flatpak ref: %s", flatpak_ref);
|
|
||||||
mct_app_filter_builder_blacklist_flatpak_ref (&builder, flatpak_ref);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
const gchar *executable = g_app_info_get_executable (G_APP_INFO (app));
|
|
||||||
g_autofree gchar *path = g_find_program_in_path (executable);
|
|
||||||
|
|
||||||
if (!path)
|
|
||||||
{
|
|
||||||
g_warning ("Skipping blacklisting executable ‘%s’ due to it not being found", executable);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
g_debug ("\t\t → Blacklisting path: %s", path);
|
|
||||||
mct_app_filter_builder_blacklist_path (&builder, path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Maturity level */
|
/* Maturity level */
|
||||||
|
|
||||||
|
@ -758,114 +536,51 @@ on_allow_web_browsers_switch_active_changed_cb (GtkSwitch *s,
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
on_switch_active_changed_cb (GtkSwitch *s,
|
on_restrict_applications_button_clicked_cb (GtkButton *button,
|
||||||
GParamSpec *pspec,
|
gpointer user_data)
|
||||||
MctUserControls *self)
|
|
||||||
{
|
{
|
||||||
GAppInfo *app;
|
MctUserControls *self = MCT_USER_CONTROLS (user_data);
|
||||||
gboolean allowed;
|
GtkWidget *toplevel;
|
||||||
|
|
||||||
app = g_object_get_data (G_OBJECT (s), "GAppInfo");
|
/* Show the restrict applications dialogue modally, making sure to update its
|
||||||
allowed = gtk_switch_get_active (s);
|
* state first. */
|
||||||
|
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
|
||||||
|
if (GTK_IS_WINDOW (toplevel))
|
||||||
|
gtk_window_set_transient_for (GTK_WINDOW (self->restrict_applications_dialog),
|
||||||
|
GTK_WINDOW (toplevel));
|
||||||
|
|
||||||
if (allowed)
|
mct_restrict_applications_dialog_set_user (self->restrict_applications_dialog, self->user);
|
||||||
{
|
mct_restrict_applications_dialog_set_app_filter (self->restrict_applications_dialog, self->filter);
|
||||||
gboolean removed;
|
|
||||||
|
|
||||||
g_debug ("Removing '%s' from blacklisted apps", g_app_info_get_id (app));
|
gtk_widget_show (GTK_WIDGET (self->restrict_applications_dialog));
|
||||||
|
}
|
||||||
|
|
||||||
removed = g_hash_table_remove (self->blacklisted_apps, app);
|
static gboolean
|
||||||
g_assert (removed);
|
on_restrict_applications_dialog_delete_event_cb (GtkWidget *widget,
|
||||||
}
|
GdkEvent *event,
|
||||||
else
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
gboolean added;
|
MctUserControls *self = MCT_USER_CONTROLS (user_data);
|
||||||
|
|
||||||
g_debug ("Blacklisting '%s'", g_app_info_get_id (app));
|
/* When the ‘Restrict Applications’ dialogue is closed, don’t destroy it,
|
||||||
|
* since it contains the app filter settings which we’ll want to reuse next
|
||||||
added = g_hash_table_add (self->blacklisted_apps, g_object_ref (app));
|
* time the dialogue is shown or the app filter is saved. */
|
||||||
g_assert (added);
|
gtk_widget_hide (GTK_WIDGET (self->restrict_applications_dialog));
|
||||||
}
|
|
||||||
|
|
||||||
|
/* Schedule an update to the saved state. */
|
||||||
schedule_update_blacklisted_apps (self);
|
schedule_update_blacklisted_apps (self);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
static GtkWidget *
|
static void
|
||||||
create_row_for_app_cb (gpointer item,
|
on_restrict_applications_dialog_response_cb (GtkDialog *dialog,
|
||||||
|
gint response_id,
|
||||||
gpointer user_data)
|
gpointer user_data)
|
||||||
{
|
{
|
||||||
g_autoptr(GIcon) icon = NULL;
|
MctUserControls *self = MCT_USER_CONTROLS (user_data);
|
||||||
MctUserControls *self;
|
|
||||||
GtkWidget *box, *w;
|
|
||||||
GAppInfo *app;
|
|
||||||
gboolean allowed;
|
|
||||||
const gchar *app_name;
|
|
||||||
gint size;
|
|
||||||
|
|
||||||
self = MCT_USER_CONTROLS (user_data);
|
on_restrict_applications_dialog_delete_event_cb (GTK_WIDGET (dialog), NULL, self);
|
||||||
app = item;
|
|
||||||
app_name = g_app_info_get_name (app);
|
|
||||||
|
|
||||||
g_assert (G_IS_DESKTOP_APP_INFO (app));
|
|
||||||
|
|
||||||
icon = g_app_info_get_icon (app);
|
|
||||||
if (icon == NULL)
|
|
||||||
icon = g_themed_icon_new ("application-x-executable");
|
|
||||||
else
|
|
||||||
g_object_ref (icon);
|
|
||||||
|
|
||||||
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12);
|
|
||||||
gtk_container_set_border_width (GTK_CONTAINER (box), 12);
|
|
||||||
gtk_widget_set_margin_end (box, 12);
|
|
||||||
|
|
||||||
/* Icon */
|
|
||||||
w = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
|
|
||||||
gtk_icon_size_lookup (GTK_ICON_SIZE_DND, &size, NULL);
|
|
||||||
gtk_image_set_pixel_size (GTK_IMAGE (w), size);
|
|
||||||
gtk_container_add (GTK_CONTAINER (box), w);
|
|
||||||
|
|
||||||
/* App name label */
|
|
||||||
w = g_object_new (GTK_TYPE_LABEL,
|
|
||||||
"label", app_name,
|
|
||||||
"hexpand", TRUE,
|
|
||||||
"xalign", 0.0,
|
|
||||||
NULL);
|
|
||||||
gtk_container_add (GTK_CONTAINER (box), w);
|
|
||||||
|
|
||||||
/* Switch */
|
|
||||||
w = g_object_new (GTK_TYPE_SWITCH,
|
|
||||||
"valign", GTK_ALIGN_CENTER,
|
|
||||||
NULL);
|
|
||||||
gtk_container_add (GTK_CONTAINER (box), w);
|
|
||||||
|
|
||||||
gtk_widget_show_all (box);
|
|
||||||
|
|
||||||
/* Fetch status from AccountService */
|
|
||||||
allowed = mct_app_filter_is_appinfo_allowed (self->filter, app);
|
|
||||||
|
|
||||||
gtk_switch_set_active (GTK_SWITCH (w), allowed);
|
|
||||||
g_object_set_data_full (G_OBJECT (w), "GAppInfo", g_object_ref (app), g_object_unref);
|
|
||||||
|
|
||||||
if (allowed)
|
|
||||||
g_hash_table_remove (self->blacklisted_apps, app);
|
|
||||||
else if (!allowed)
|
|
||||||
g_hash_table_add (self->blacklisted_apps, g_object_ref (app));
|
|
||||||
|
|
||||||
g_signal_connect (w, "notify::active", G_CALLBACK (on_switch_active_changed_cb), self);
|
|
||||||
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
static gint
|
|
||||||
compare_app_info_cb (gconstpointer a,
|
|
||||||
gconstpointer b,
|
|
||||||
gpointer user_data)
|
|
||||||
{
|
|
||||||
GAppInfo *app_a = (GAppInfo*) a;
|
|
||||||
GAppInfo *app_b = (GAppInfo*) b;
|
|
||||||
|
|
||||||
return g_utf8_collate (g_app_info_get_display_name (app_a),
|
|
||||||
g_app_info_get_display_name (app_b));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -889,7 +604,7 @@ on_set_age_action_activated (GSimpleAction *action,
|
||||||
|
|
||||||
/* Update the button */
|
/* Update the button */
|
||||||
if (age == oars_disabled_age)
|
if (age == oars_disabled_age)
|
||||||
gtk_button_set_label (self->restriction_button, _("No Restriction"));
|
gtk_button_set_label (self->restriction_button, _("All Ages"));
|
||||||
|
|
||||||
for (i = 0; age != oars_disabled_age && entries[i] != NULL; i++)
|
for (i = 0; age != oars_disabled_age && entries[i] != NULL; i++)
|
||||||
{
|
{
|
||||||
|
@ -923,11 +638,8 @@ mct_user_controls_finalize (GObject *object)
|
||||||
|
|
||||||
g_cancellable_cancel (self->cancellable);
|
g_cancellable_cancel (self->cancellable);
|
||||||
g_clear_object (&self->action_group);
|
g_clear_object (&self->action_group);
|
||||||
g_clear_object (&self->apps);
|
|
||||||
g_clear_object (&self->cancellable);
|
g_clear_object (&self->cancellable);
|
||||||
g_clear_object (&self->system_installation);
|
|
||||||
g_clear_object (&self->user);
|
g_clear_object (&self->user);
|
||||||
g_clear_object (&self->user_installation);
|
|
||||||
|
|
||||||
if (self->permission != NULL && self->permission_allowed_id != 0)
|
if (self->permission != NULL && self->permission_allowed_id != 0)
|
||||||
{
|
{
|
||||||
|
@ -936,10 +648,11 @@ mct_user_controls_finalize (GObject *object)
|
||||||
}
|
}
|
||||||
g_clear_object (&self->permission);
|
g_clear_object (&self->permission);
|
||||||
|
|
||||||
g_clear_pointer (&self->blacklisted_apps, g_hash_table_unref);
|
|
||||||
g_clear_pointer (&self->filter, mct_app_filter_unref);
|
g_clear_pointer (&self->filter, mct_app_filter_unref);
|
||||||
g_clear_object (&self->manager);
|
g_clear_object (&self->manager);
|
||||||
g_clear_object (&self->app_info_monitor);
|
|
||||||
|
/* Hopefully we don’t have data loss. */
|
||||||
|
g_assert (self->flushed_on_dispose);
|
||||||
|
|
||||||
G_OBJECT_CLASS (mct_user_controls_parent_class)->finalize (object);
|
G_OBJECT_CLASS (mct_user_controls_parent_class)->finalize (object);
|
||||||
}
|
}
|
||||||
|
@ -950,7 +663,13 @@ mct_user_controls_dispose (GObject *object)
|
||||||
{
|
{
|
||||||
MctUserControls *self = (MctUserControls *)object;
|
MctUserControls *self = (MctUserControls *)object;
|
||||||
|
|
||||||
|
/* Since GTK calls g_object_run_dispose(), dispose() may be called multiple
|
||||||
|
* times. We definitely want to save any unsaved changes, but don’t need to
|
||||||
|
* do it multiple times, and after the first g_object_run_dispose() call,
|
||||||
|
* none of our child widgets are still around to extract data from anyway. */
|
||||||
|
if (!self->flushed_on_dispose)
|
||||||
flush_update_blacklisted_apps (self);
|
flush_update_blacklisted_apps (self);
|
||||||
|
self->flushed_on_dispose = TRUE;
|
||||||
|
|
||||||
G_OBJECT_CLASS (mct_user_controls_parent_class)->dispose (object);
|
G_OBJECT_CLASS (mct_user_controls_parent_class)->dispose (object);
|
||||||
}
|
}
|
||||||
|
@ -1038,10 +757,13 @@ mct_user_controls_class_init (MctUserControlsClass *klass)
|
||||||
gtk_widget_class_bind_template_child (widget_class, MctUserControls, allow_web_browsers_switch);
|
gtk_widget_class_bind_template_child (widget_class, MctUserControls, allow_web_browsers_switch);
|
||||||
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restriction_button);
|
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restriction_button);
|
||||||
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restriction_popover);
|
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restriction_popover);
|
||||||
gtk_widget_class_bind_template_child (widget_class, MctUserControls, listbox);
|
gtk_widget_class_bind_template_child (widget_class, MctUserControls, restrict_applications_dialog);
|
||||||
|
|
||||||
gtk_widget_class_bind_template_callback (widget_class, on_allow_installation_switch_active_changed_cb);
|
gtk_widget_class_bind_template_callback (widget_class, on_allow_installation_switch_active_changed_cb);
|
||||||
gtk_widget_class_bind_template_callback (widget_class, on_allow_web_browsers_switch_active_changed_cb);
|
gtk_widget_class_bind_template_callback (widget_class, on_allow_web_browsers_switch_active_changed_cb);
|
||||||
|
gtk_widget_class_bind_template_callback (widget_class, on_restrict_applications_button_clicked_cb);
|
||||||
|
gtk_widget_class_bind_template_callback (widget_class, on_restrict_applications_dialog_delete_event_cb);
|
||||||
|
gtk_widget_class_bind_template_callback (widget_class, on_restrict_applications_dialog_response_cb);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -1050,11 +772,12 @@ mct_user_controls_init (MctUserControls *self)
|
||||||
g_autoptr(GDBusConnection) system_bus = NULL;
|
g_autoptr(GDBusConnection) system_bus = NULL;
|
||||||
g_autoptr(GError) error = NULL;
|
g_autoptr(GError) error = NULL;
|
||||||
|
|
||||||
|
/* Ensure the types used in the UI are registered. */
|
||||||
|
g_type_ensure (MCT_TYPE_RESTRICT_APPLICATIONS_DIALOG);
|
||||||
|
|
||||||
gtk_widget_init_template (GTK_WIDGET (self));
|
gtk_widget_init_template (GTK_WIDGET (self));
|
||||||
|
|
||||||
self->selected_age = (guint) -1;
|
self->selected_age = (guint) -1;
|
||||||
self->system_installation = flatpak_installation_new_system (NULL, NULL);
|
|
||||||
self->user_installation = flatpak_installation_new_user (NULL, NULL);
|
|
||||||
|
|
||||||
self->cancellable = g_cancellable_new ();
|
self->cancellable = g_cancellable_new ();
|
||||||
|
|
||||||
|
@ -1079,19 +802,6 @@ mct_user_controls_init (MctUserControls *self)
|
||||||
G_ACTION_GROUP (self->action_group));
|
G_ACTION_GROUP (self->action_group));
|
||||||
|
|
||||||
gtk_popover_bind_model (self->restriction_popover, G_MENU_MODEL (self->age_menu), NULL);
|
gtk_popover_bind_model (self->restriction_popover, G_MENU_MODEL (self->age_menu), NULL);
|
||||||
self->blacklisted_apps = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, NULL);
|
|
||||||
|
|
||||||
self->apps = g_list_store_new (G_TYPE_APP_INFO);
|
|
||||||
|
|
||||||
self->app_info_monitor = g_app_info_monitor_get ();
|
|
||||||
g_signal_connect_object (self->app_info_monitor, "changed",
|
|
||||||
(GCallback) app_info_changed_cb, self, 0);
|
|
||||||
|
|
||||||
gtk_list_box_bind_model (self->listbox,
|
|
||||||
G_LIST_MODEL (self->apps),
|
|
||||||
create_row_for_app_cb,
|
|
||||||
self,
|
|
||||||
NULL);
|
|
||||||
|
|
||||||
g_object_bind_property (self->allow_user_installation_switch, "active",
|
g_object_bind_property (self->allow_user_installation_switch, "active",
|
||||||
self->allow_system_installation_switch, "sensitive",
|
self->allow_system_installation_switch, "sensitive",
|
||||||
|
@ -1133,6 +843,7 @@ on_permission_allowed_cb (GObject *obj,
|
||||||
{
|
{
|
||||||
MctUserControls *self = MCT_USER_CONTROLS (user_data);
|
MctUserControls *self = MCT_USER_CONTROLS (user_data);
|
||||||
|
|
||||||
|
update_app_filter (self);
|
||||||
setup_parental_control_settings (self);
|
setup_parental_control_settings (self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1172,6 +883,7 @@ mct_user_controls_set_permission (MctUserControls *self,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle changes. */
|
/* Handle changes. */
|
||||||
|
update_app_filter (self);
|
||||||
setup_parental_control_settings (self);
|
setup_parental_control_settings (self);
|
||||||
|
|
||||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PERMISSION]);
|
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PERMISSION]);
|
||||||
|
|
|
@ -9,18 +9,15 @@
|
||||||
<property name="column-spacing">12</property>
|
<property name="column-spacing">12</property>
|
||||||
<property name="valign">start</property>
|
<property name="valign">start</property>
|
||||||
|
|
||||||
<!-- Restricted Apps -->
|
<!-- Application Usage Restrictions -->
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel">
|
<object class="GtkLabel">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="xalign">0.0</property>
|
<property name="xalign">0.0</property>
|
||||||
<property name="label" translatable="yes">Restrict Apps</property>
|
<property name="label" translatable="yes">Application Usage Restrictions</property>
|
||||||
<attributes>
|
<attributes>
|
||||||
<attribute name="weight" value="bold" />
|
<attribute name="weight" value="bold" />
|
||||||
</attributes>
|
</attributes>
|
||||||
<accessibility>
|
|
||||||
<relation target="listbox" type="label-for"/>
|
|
||||||
</accessibility>
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="top-attach">0</property>
|
<property name="top-attach">0</property>
|
||||||
|
@ -29,22 +26,187 @@
|
||||||
</child>
|
</child>
|
||||||
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel">
|
<object class="GtkFrame">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="xalign">0.0</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="label" translatable="yes">Prevent this user from opening some apps by turning them off below.</property>
|
<property name="hexpand">True</property>
|
||||||
<property name="wrap">True</property>
|
<property name="label_xalign">0</property>
|
||||||
<property name="use-underline">True</property>
|
<property name="shadow_type">in</property>
|
||||||
<property name="mnemonic-widget">listbox</property>
|
<child>
|
||||||
<attributes>
|
<object class="GtkListBox">
|
||||||
<attribute name="scale" value="0.83333" />
|
<property name="visible">True</property>
|
||||||
</attributes>
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="selection_mode">none</property>
|
||||||
|
<property name="activate_on_single_click">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="margin-left">12</property>
|
||||||
|
<property name="margin-right">12</property>
|
||||||
|
<property name="margin-top">8</property>
|
||||||
|
<property name="margin-bottom">8</property>
|
||||||
|
<property name="row-spacing">4</property>
|
||||||
|
<property name="column-spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="allow_web_browsers_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Allow _Web Browsers</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">allow_web_browsers_switch</property>
|
||||||
<accessibility>
|
<accessibility>
|
||||||
<relation target="listbox" type="description-for"/>
|
<relation target="allow_web_browsers_switch" type="label-for"/>
|
||||||
</accessibility>
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="allow_web_browsers_description">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Prevents the user from running web browsers, but limited web content may still be available in other applications</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="scale" value="0.88"/>
|
||||||
|
</attributes>
|
||||||
<style>
|
<style>
|
||||||
<class name="dim-label" />
|
<class name="dim-label" />
|
||||||
</style>
|
</style>
|
||||||
|
<accessibility>
|
||||||
|
<relation target="allow_web_browsers_switch" type="description-for"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="allow_web_browsers_switch">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<signal name="notify::active" handler="on_allow_web_browsers_switch_active_changed_cb" object="MctUserControls" swapped="no" />
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
<property name="height">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="margin-left">12</property>
|
||||||
|
<property name="margin-right">12</property>
|
||||||
|
<property name="margin-top">8</property>
|
||||||
|
<property name="margin-bottom">8</property>
|
||||||
|
<property name="row-spacing">4</property>
|
||||||
|
<property name="column-spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="restrict_applications_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">_Restrict Applications</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">restrict_applications_button</property>
|
||||||
|
<accessibility>
|
||||||
|
<relation target="restrict_applications_button" type="label-for"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="restrict_applications_description">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Prevents particular applications from being used</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="scale" value="0.88"/>
|
||||||
|
</attributes>
|
||||||
|
<style>
|
||||||
|
<class name="dim-label" />
|
||||||
|
</style>
|
||||||
|
<accessibility>
|
||||||
|
<relation target="restrict_applications_button" type="description-for"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="restrict_applications_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="relief">none</property>
|
||||||
|
<signal name="clicked" handler="on_restrict_applications_button_clicked_cb" object="MctUserControls" swapped="no" />
|
||||||
|
<child>
|
||||||
|
<object class="GtkImage">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="icon-name">pan-end-symbolic</property>
|
||||||
|
<property name="icon-size">4</property><!-- GTK_ICON_SIZE_BUTTON -->
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
<property name="height">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="top-attach">1</property>
|
<property name="top-attach">1</property>
|
||||||
|
@ -52,251 +214,285 @@
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
|
||||||
|
<!-- Software Installation Restrictions -->
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkScrolledWindow">
|
<object class="GtkLabel">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="hexpand">True</property>
|
<property name="margin-top">12</property>
|
||||||
<property name="hscrollbar-policy">never</property>
|
<property name="xalign">0.0</property>
|
||||||
<property name="min-content-height">100</property>
|
<property name="label" translatable="yes">Software Installation Restrictions</property>
|
||||||
<property name="max-content-height">400</property>
|
<attributes>
|
||||||
<property name="propagate-natural-height">True</property>
|
<attribute name="weight" value="bold" />
|
||||||
<property name="shadow-type">etched-in</property>
|
</attributes>
|
||||||
|
|
||||||
<!-- Restricted Apps Listbox -->
|
|
||||||
<child>
|
|
||||||
<object class="GtkListBox" id="listbox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="selection-mode">none</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
</object>
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="top-attach">2</property>
|
<property name="top-attach">2</property>
|
||||||
<property name="left-attach">0</property>
|
<property name="left-attach">0</property>
|
||||||
<property name="width">2</property>
|
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
|
||||||
<!-- Restricted Web Browsers -->
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel">
|
<object class="GtkFrame">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="xalign">0.0</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="label" translatable="yes">Restrict Web Browsers</property>
|
<property name="hexpand">True</property>
|
||||||
<attributes>
|
<property name="label_xalign">0</property>
|
||||||
<attribute name="weight" value="bold" />
|
<property name="shadow_type">in</property>
|
||||||
</attributes>
|
<child>
|
||||||
|
<object class="GtkListBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="selection_mode">none</property>
|
||||||
|
<property name="activate_on_single_click">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible" bind-source="allow_user_installation_switch" bind-property="visible" bind-flags="default|sync-create" />
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="margin-left">12</property>
|
||||||
|
<property name="margin-right">12</property>
|
||||||
|
<property name="margin-top">8</property>
|
||||||
|
<property name="margin-bottom">8</property>
|
||||||
|
<property name="row-spacing">4</property>
|
||||||
|
<property name="column-spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="allow_user_installation_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Application _Installation</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">allow_user_installation_switch</property>
|
||||||
<accessibility>
|
<accessibility>
|
||||||
<relation target="allow_web_browsers_switch" type="label-for"/>
|
<relation target="allow_user_installation_switch" type="label-for"/>
|
||||||
</accessibility>
|
</accessibility>
|
||||||
</object>
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="allow_user_installation_description">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Restricts the user from installing applications</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="scale" value="0.88"/>
|
||||||
|
</attributes>
|
||||||
|
<style>
|
||||||
|
<class name="dim-label" />
|
||||||
|
</style>
|
||||||
|
<accessibility>
|
||||||
|
<relation target="allow_user_installation_switch" type="description-for"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="allow_user_installation_switch">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<signal name="notify::active" handler="on_allow_installation_switch_active_changed_cb" object="MctUserControls" swapped="no" />
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
<property name="height">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible" bind-source="allow_system_installation_switch" bind-property="visible" bind-flags="default|sync-create" />
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="margin-left">12</property>
|
||||||
|
<property name="margin-right">12</property>
|
||||||
|
<property name="margin-top">8</property>
|
||||||
|
<property name="margin-bottom">8</property>
|
||||||
|
<property name="row-spacing">4</property>
|
||||||
|
<property name="column-spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="allow_system_installation_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Application Installation for _Others</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">allow_system_installation_switch</property>
|
||||||
|
<accessibility>
|
||||||
|
<relation target="allow_system_installation_switch" type="label-for"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="allow_system_installation_description">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Restricts the user from installing applications for all users</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="scale" value="0.88"/>
|
||||||
|
</attributes>
|
||||||
|
<style>
|
||||||
|
<class name="dim-label" />
|
||||||
|
</style>
|
||||||
|
<accessibility>
|
||||||
|
<relation target="allow_system_installation_switch" type="description-for"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkSwitch" id="allow_system_installation_switch">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<signal name="notify::active" handler="on_allow_installation_switch_active_changed_cb" object="MctUserControls" swapped="no" />
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
<property name="height">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
|
||||||
|
<child>
|
||||||
|
<object class="GtkListBoxRow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="activatable">False</property>
|
||||||
|
<property name="selectable">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkGrid">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="margin-left">12</property>
|
||||||
|
<property name="margin-right">12</property>
|
||||||
|
<property name="margin-top">8</property>
|
||||||
|
<property name="margin-bottom">8</property>
|
||||||
|
<property name="row-spacing">4</property>
|
||||||
|
<property name="column-spacing">4</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="restriction_label">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Application _Suitability</property>
|
||||||
|
<property name="use_underline">True</property>
|
||||||
|
<property name="mnemonic_widget">restriction_button</property>
|
||||||
|
<accessibility>
|
||||||
|
<relation target="restriction_button" type="label-for"/>
|
||||||
|
<relation target="restriction_button" type="flows-to"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="restriction_description">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="halign">start</property>
|
||||||
|
<property name="hexpand">True</property>
|
||||||
|
<property name="ellipsize">end</property>
|
||||||
|
<property name="xalign">0</property>
|
||||||
|
<property name="label" translatable="yes">Restricts the applications the user can browse or install to those suitable for certain ages</property>
|
||||||
|
<attributes>
|
||||||
|
<attribute name="scale" value="0.88"/>
|
||||||
|
</attributes>
|
||||||
|
<style>
|
||||||
|
<class name="dim-label" />
|
||||||
|
</style>
|
||||||
|
<accessibility>
|
||||||
|
<relation target="restriction_button" type="description-for"/>
|
||||||
|
</accessibility>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">0</property>
|
||||||
|
<property name="top-attach">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuButton" id="restriction_button">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="halign">end</property>
|
||||||
|
<property name="valign">center</property>
|
||||||
|
<property name="popover">restriction_popover</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="left-attach">1</property>
|
||||||
|
<property name="top-attach">0</property>
|
||||||
|
<property name="height">2</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="top-attach">3</property>
|
<property name="top-attach">3</property>
|
||||||
<property name="left-attach">0</property>
|
<property name="left-attach">0</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="xalign">0.0</property>
|
|
||||||
<property name="label" translatable="yes">Prevent this user from running web browsers by turning them off below. Note that if the computer is connected to the internet, limited web content may still be available in other applications.</property>
|
|
||||||
<property name="wrap">True</property>
|
|
||||||
<property name="max-width-chars">55</property>
|
|
||||||
<property name="use-underline">True</property>
|
|
||||||
<property name="mnemonic-widget">allow_web_browsers_switch</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="scale" value="0.83333" />
|
|
||||||
</attributes>
|
|
||||||
<accessibility>
|
|
||||||
<relation target="allow_web_browsers_switch" type="description-for"/>
|
|
||||||
</accessibility>
|
|
||||||
<style>
|
|
||||||
<class name="dim-label" />
|
|
||||||
</style>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="top-attach">4</property>
|
|
||||||
<property name="left-attach">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="browsers_label">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="xalign">1.0</property>
|
|
||||||
<property name="label" translatable="yes">Web _Browsers</property>
|
|
||||||
<property name="wrap">True</property>
|
|
||||||
<property name="use-underline">True</property>
|
|
||||||
<property name="mnemonic-widget">allow_web_browsers_switch</property>
|
|
||||||
<style>
|
|
||||||
<class name="dim-label" />
|
|
||||||
</style>
|
|
||||||
<accessibility>
|
|
||||||
<relation target="allow_web_browsers_switch" type="label-for"/>
|
|
||||||
</accessibility>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkSwitch" id="allow_web_browsers_switch">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="hexpand">True</property>
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<signal name="notify::active" handler="on_allow_web_browsers_switch_active_changed_cb" object="MctUserControls" swapped="no" />
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="top-attach">5</property>
|
|
||||||
<property name="left-attach">0</property>
|
|
||||||
<property name="width">2</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<!-- App Center Restrictions -->
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="margin-top">12</property>
|
|
||||||
<property name="xalign">0.0</property>
|
|
||||||
<property name="label" translatable="yes">App Center Restrictions</property>
|
|
||||||
<attributes>
|
|
||||||
<attribute name="weight" value="bold" />
|
|
||||||
</attributes>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="top-attach">6</property>
|
|
||||||
<property name="left-attach">0</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="user_installation_label">
|
|
||||||
<property name="visible" bind-source="allow_user_installation_switch" bind-property="visible" bind-flags="default|sync-create" />
|
|
||||||
<property name="xalign">1.0</property>
|
|
||||||
<property name="label" translatable="yes">App _Installation</property>
|
|
||||||
<property name="wrap">True</property>
|
|
||||||
<property name="use-underline">True</property>
|
|
||||||
<property name="mnemonic-widget">allow_user_installation_switch</property>
|
|
||||||
<style>
|
|
||||||
<class name="dim-label" />
|
|
||||||
</style>
|
|
||||||
<accessibility>
|
|
||||||
<relation target="allow_user_installation_switch" type="label-for"/>
|
|
||||||
</accessibility>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkSwitch" id="allow_user_installation_switch">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="hexpand">True</property>
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<signal name="notify::active" handler="on_allow_installation_switch_active_changed_cb" object="MctUserControls" swapped="no" />
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="top-attach">7</property>
|
|
||||||
<property name="left-attach">0</property>
|
|
||||||
<property name="width">2</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="system_installation_label">
|
|
||||||
<property name="visible" bind-source="allow_system_installation_switch" bind-property="visible" bind-flags="default|sync-create" />
|
|
||||||
<property name="xalign">1.0</property>
|
|
||||||
<property name="label" translatable="yes">Install Apps for All _Users</property>
|
|
||||||
<property name="wrap">True</property>
|
|
||||||
<property name="use-underline">True</property>
|
|
||||||
<property name="mnemonic-widget">allow_system_installation_switch</property>
|
|
||||||
<style>
|
|
||||||
<class name="dim-label" />
|
|
||||||
</style>
|
|
||||||
<accessibility>
|
|
||||||
<relation target="allow_system_installation_switch" type="label-for"/>
|
|
||||||
</accessibility>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkSwitch" id="allow_system_installation_switch">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="hexpand">True</property>
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<signal name="notify::active" handler="on_allow_installation_switch_active_changed_cb" object="MctUserControls" swapped="no" />
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="top-attach">8</property>
|
|
||||||
<property name="left-attach">0</property>
|
|
||||||
<property name="width">2</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkBox">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="spacing">12</property>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkLabel" id="app_restriction_label">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="xalign">1.0</property>
|
|
||||||
<property name="label" translatable="yes">Show Apps _Suitable For</property>
|
|
||||||
<property name="wrap">True</property>
|
|
||||||
<property name="use-underline">True</property>
|
|
||||||
<property name="mnemonic-widget">restriction_button</property>
|
|
||||||
<style>
|
|
||||||
<class name="dim-label" />
|
|
||||||
</style>
|
|
||||||
<accessibility>
|
|
||||||
<relation target="restriction_button" type="label-for"/>
|
|
||||||
<relation target="restriction_button" type="flows-to"/>
|
|
||||||
</accessibility>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
<child>
|
|
||||||
<object class="GtkMenuButton" id="restriction_button">
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="hexpand">True</property>
|
|
||||||
<property name="halign">start</property>
|
|
||||||
<property name="popover">restriction_popover</property>
|
|
||||||
<property name="width-request">200</property>
|
|
||||||
</object>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="top-attach">9</property>
|
|
||||||
<property name="left-attach">0</property>
|
|
||||||
<property name="width">2</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<object class="GtkPopoverMenu" id="restriction_popover">
|
<object class="GtkPopoverMenu" id="restriction_popover">
|
||||||
|
@ -318,10 +514,22 @@
|
||||||
<object class="GtkSizeGroup">
|
<object class="GtkSizeGroup">
|
||||||
<property name="mode">horizontal</property>
|
<property name="mode">horizontal</property>
|
||||||
<widgets>
|
<widgets>
|
||||||
<widget name="browsers_label" />
|
<widget name="allow_web_browsers_label" />
|
||||||
<widget name="app_restriction_label" />
|
<widget name="allow_web_browsers_description" />
|
||||||
<widget name="user_installation_label" />
|
<widget name="restrict_applications_label" />
|
||||||
<widget name="system_installation_label" />
|
<widget name="restrict_applications_description" />
|
||||||
|
<widget name="restriction_label" />
|
||||||
|
<widget name="allow_user_installation_label" />
|
||||||
|
<widget name="allow_system_installation_label" />
|
||||||
</widgets>
|
</widgets>
|
||||||
</object>
|
</object>
|
||||||
|
|
||||||
|
<object class="MctRestrictApplicationsDialog" id="restrict_applications_dialog">
|
||||||
|
<property name="visible">False</property>
|
||||||
|
<property name="modal">True</property>
|
||||||
|
<property name="destroy-with-parent">False</property>
|
||||||
|
<property name="use-header-bar">1</property>
|
||||||
|
<signal name="delete-event" handler="on_restrict_applications_dialog_delete_event_cb" />
|
||||||
|
<signal name="response" handler="on_restrict_applications_dialog_response_cb" />
|
||||||
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
|
@ -63,6 +63,7 @@ struct _MctUserSelector
|
||||||
|
|
||||||
ActUserManager *user_manager; /* (owned) */
|
ActUserManager *user_manager; /* (owned) */
|
||||||
ActUser *user; /* (owned) */
|
ActUser *user; /* (owned) */
|
||||||
|
gboolean show_administrators;
|
||||||
};
|
};
|
||||||
|
|
||||||
G_DEFINE_TYPE (MctUserSelector, mct_user_selector, GTK_TYPE_BOX)
|
G_DEFINE_TYPE (MctUserSelector, mct_user_selector, GTK_TYPE_BOX)
|
||||||
|
@ -71,9 +72,10 @@ typedef enum
|
||||||
{
|
{
|
||||||
PROP_USER = 1,
|
PROP_USER = 1,
|
||||||
PROP_USER_MANAGER,
|
PROP_USER_MANAGER,
|
||||||
|
PROP_SHOW_ADMINISTRATORS,
|
||||||
} MctUserSelectorProperty;
|
} MctUserSelectorProperty;
|
||||||
|
|
||||||
static GParamSpec *properties[PROP_USER_MANAGER + 1];
|
static GParamSpec *properties[PROP_SHOW_ADMINISTRATORS + 1];
|
||||||
|
|
||||||
static void
|
static void
|
||||||
mct_user_selector_constructed (GObject *obj)
|
mct_user_selector_constructed (GObject *obj)
|
||||||
|
@ -117,6 +119,10 @@ mct_user_selector_get_property (GObject *object,
|
||||||
g_value_set_object (value, self->user_manager);
|
g_value_set_object (value, self->user_manager);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_SHOW_ADMINISTRATORS:
|
||||||
|
g_value_set_boolean (value, self->show_administrators);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
}
|
}
|
||||||
|
@ -142,6 +148,11 @@ mct_user_selector_set_property (GObject *object,
|
||||||
self->user_manager = g_value_dup_object (value);
|
self->user_manager = g_value_dup_object (value);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case PROP_SHOW_ADMINISTRATORS:
|
||||||
|
self->show_administrators = g_value_get_boolean (value);
|
||||||
|
reload_users (self, NULL);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
}
|
}
|
||||||
|
@ -211,6 +222,21 @@ mct_user_selector_class_init (MctUserSelectorClass *klass)
|
||||||
G_PARAM_STATIC_STRINGS |
|
G_PARAM_STATIC_STRINGS |
|
||||||
G_PARAM_EXPLICIT_NOTIFY);
|
G_PARAM_EXPLICIT_NOTIFY);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MctUserSelector:show-administrators:
|
||||||
|
*
|
||||||
|
* Whether to show administrators in the list, or hide them.
|
||||||
|
*
|
||||||
|
* Since: 0.5.0
|
||||||
|
*/
|
||||||
|
properties[PROP_SHOW_ADMINISTRATORS] =
|
||||||
|
g_param_spec_boolean ("show-administrators",
|
||||||
|
"Show Administrators?",
|
||||||
|
"Whether to show administrators in the list, or hide them.",
|
||||||
|
TRUE,
|
||||||
|
G_PARAM_READWRITE |
|
||||||
|
G_PARAM_STATIC_STRINGS);
|
||||||
|
|
||||||
g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
|
g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
|
||||||
|
|
||||||
gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/user-selector.ui");
|
gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/user-selector.ui");
|
||||||
|
@ -223,6 +249,8 @@ mct_user_selector_class_init (MctUserSelectorClass *klass)
|
||||||
static void
|
static void
|
||||||
mct_user_selector_init (MctUserSelector *self)
|
mct_user_selector_init (MctUserSelector *self)
|
||||||
{
|
{
|
||||||
|
self->show_administrators = TRUE;
|
||||||
|
|
||||||
/* Ensure the types used in the UI are registered. */
|
/* Ensure the types used in the UI are registered. */
|
||||||
g_type_ensure (MCT_TYPE_CAROUSEL);
|
g_type_ensure (MCT_TYPE_CAROUSEL);
|
||||||
|
|
||||||
|
@ -351,6 +379,14 @@ reload_users (MctUserSelector *self,
|
||||||
for (l = list; l; l = l->next)
|
for (l = list; l; l = l->next)
|
||||||
{
|
{
|
||||||
user = l->data;
|
user = l->data;
|
||||||
|
|
||||||
|
if (act_user_get_account_type (user) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR &&
|
||||||
|
!self->show_administrators)
|
||||||
|
{
|
||||||
|
g_debug ("Ignoring administrator %s", get_real_or_user_name (user));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
g_debug ("Adding user %s", get_real_or_user_name (user));
|
g_debug ("Adding user %s", get_real_or_user_name (user));
|
||||||
user_added_cb (self->user_manager, user, self);
|
user_added_cb (self->user_manager, user, self);
|
||||||
}
|
}
|
||||||
|
@ -410,7 +446,9 @@ user_added_cb (ActUserManager *user_manager,
|
||||||
MctUserSelector *self = MCT_USER_SELECTOR (user_data);
|
MctUserSelector *self = MCT_USER_SELECTOR (user_data);
|
||||||
GtkWidget *item, *widget;
|
GtkWidget *item, *widget;
|
||||||
|
|
||||||
if (act_user_is_system_account (user))
|
if (act_user_is_system_account (user) ||
|
||||||
|
(act_user_get_account_type (user) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR &&
|
||||||
|
!self->show_administrators))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
g_debug ("User added: %u %s", (guint) act_user_get_uid (user), get_real_or_user_name (user));
|
g_debug ("User added: %u %s", (guint) act_user_get_uid (user), get_real_or_user_name (user));
|
||||||
|
|
|
@ -7,6 +7,11 @@ malcontent-control/gs-content-rating.c
|
||||||
malcontent-control/main.ui
|
malcontent-control/main.ui
|
||||||
malcontent-control/org.freedesktop.MalcontentControl.appdata.xml.in
|
malcontent-control/org.freedesktop.MalcontentControl.appdata.xml.in
|
||||||
malcontent-control/org.freedesktop.MalcontentControl.desktop.in
|
malcontent-control/org.freedesktop.MalcontentControl.desktop.in
|
||||||
|
malcontent-control/org.freedesktop.MalcontentControl.policy.in
|
||||||
|
malcontent-control/restrict-applications-dialog.c
|
||||||
|
malcontent-control/restrict-applications-dialog.ui
|
||||||
|
malcontent-control/restrict-applications-selector.c
|
||||||
|
malcontent-control/restrict-applications-selector.ui
|
||||||
malcontent-control/user-controls.c
|
malcontent-control/user-controls.c
|
||||||
malcontent-control/user-controls.ui
|
malcontent-control/user-controls.ui
|
||||||
pam/pam_malcontent.c
|
pam/pam_malcontent.c
|
||||||
|
|
Loading…
Reference in New Issue