Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2019 Endless Mobile, Inc.
4 : : *
5 : : * This program is free software; you can redistribute it and/or modify
6 : : * it under the terms of the GNU General Public License as published by
7 : : * the Free Software Foundation; either version 2 of the License, or
8 : : * (at your option) any later version.
9 : : *
10 : : * This program is distributed in the hope that it will be useful,
11 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : : * GNU General Public License for more details.
14 : : *
15 : : * You should have received a copy of the GNU General Public License
16 : : * along with this program; if not, see <http://www.gnu.org/licenses/>.
17 : : *
18 : : * Authors:
19 : : * - Philip Withnall <withnall@endlessm.com>
20 : : */
21 : :
22 : : #include "config.h"
23 : :
24 : : #include <act/act.h>
25 : : #include <glib.h>
26 : : #include <glib-object.h>
27 : : #include <glib/gi18n-lib.h>
28 : : #include <gio/gio.h>
29 : : #include <gtk/gtk.h>
30 : : #include <libmalcontent-ui/malcontent-ui.h>
31 : : #include <polkit/polkit.h>
32 : :
33 : : #include "application.h"
34 : : #include "user-selector.h"
35 : :
36 : :
37 : : static void user_selector_notify_user_cb (GObject *obj,
38 : : GParamSpec *pspec,
39 : : gpointer user_data);
40 : : static void user_manager_notify_is_loaded_cb (GObject *obj,
41 : : GParamSpec *pspec,
42 : : gpointer user_data);
43 : : static void permission_new_cb (GObject *source_object,
44 : : GAsyncResult *result,
45 : : gpointer user_data);
46 : : static void permission_notify_allowed_cb (GObject *obj,
47 : : GParamSpec *pspec,
48 : : gpointer user_data);
49 : : static void user_accounts_panel_button_clicked_cb (GtkButton *button,
50 : : gpointer user_data);
51 : : static void about_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data);
52 : : static void help_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data);
53 : : static void quit_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data);
54 : :
55 : :
56 : : /**
57 : : * MctApplication:
58 : : *
59 : : * #MctApplication is a top-level object representing the parental controls
60 : : * application.
61 : : *
62 : : * Since: 0.5.0
63 : : */
64 : : struct _MctApplication
65 : : {
66 : : GtkApplication parent_instance;
67 : :
68 : : GCancellable *cancellable; /* (owned) */
69 : :
70 : : GDBusConnection *dbus_connection; /* (owned) */
71 : : ActUserManager *user_manager; /* (owned) */
72 : :
73 : : GPermission *permission; /* (owned) */
74 : : GError *permission_error; /* (nullable) (owned) */
75 : :
76 : : MctUserSelector *user_selector;
77 : : MctUserControls *user_controls;
78 : : GtkStack *main_stack;
79 : : GtkLabel *error_title;
80 : : GtkLabel *error_message;
81 : : GtkLockButton *lock_button;
82 : : GtkButton *user_accounts_panel_button;
83 : : GtkLabel *help_label;
84 : : };
85 : :
86 [ # # # # : 0 : G_DEFINE_TYPE (MctApplication, mct_application, GTK_TYPE_APPLICATION)
# # ]
87 : :
88 : : static void
89 : 0 : mct_application_init (MctApplication *self)
90 : : {
91 : 0 : self->cancellable = g_cancellable_new ();
92 : 0 : }
93 : :
94 : : static void
95 : 0 : mct_application_constructed (GObject *object)
96 : : {
97 : 0 : GApplication *application = G_APPLICATION (object);
98 : 0 : const GOptionEntry options[] =
99 : : {
100 : : { "user", 'u', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, NULL,
101 : : /* Translators: This documents the --user command line option to malcontent-control: */
102 : : N_("User to select in the UI"),
103 : : /* Translators: This is a placeholder for a command line argument value: */
104 : : N_("USERNAME") },
105 : : { NULL, },
106 : : };
107 : :
108 : 0 : g_application_set_application_id (application, "org.freedesktop.MalcontentControl");
109 : :
110 : 0 : g_application_add_main_option_entries (application, options);
111 : 0 : g_application_set_flags (application, g_application_get_flags (application) | G_APPLICATION_HANDLES_COMMAND_LINE);
112 : :
113 : : /* Translators: This is a summary of what the application does, displayed when
114 : : * it’s run with --help: */
115 : 0 : g_application_set_option_context_parameter_string (application,
116 : : N_("— view and edit parental controls"));
117 : :
118 : : /* Localisation */
119 : 0 : bindtextdomain ("malcontent", PACKAGE_LOCALE_DIR);
120 : 0 : bind_textdomain_codeset ("malcontent", "UTF-8");
121 : 0 : textdomain ("malcontent");
122 : :
123 : 0 : g_set_application_name (_("Parental Controls"));
124 : 0 : gtk_window_set_default_icon_name ("org.freedesktop.MalcontentControl");
125 : :
126 : 0 : G_OBJECT_CLASS (mct_application_parent_class)->constructed (object);
127 : 0 : }
128 : :
129 : : static void
130 : 0 : mct_application_dispose (GObject *object)
131 : : {
132 : 0 : MctApplication *self = MCT_APPLICATION (object);
133 : :
134 : 0 : g_cancellable_cancel (self->cancellable);
135 : :
136 [ # # ]: 0 : if (self->user_manager != NULL)
137 : : {
138 : 0 : g_signal_handlers_disconnect_by_func (self->user_manager,
139 : : user_manager_notify_is_loaded_cb, self);
140 [ # # ]: 0 : g_clear_object (&self->user_manager);
141 : : }
142 : :
143 [ # # ]: 0 : if (self->permission != NULL)
144 : : {
145 : 0 : g_signal_handlers_disconnect_by_func (self->permission,
146 : : permission_notify_allowed_cb, self);
147 [ # # ]: 0 : g_clear_object (&self->permission);
148 : : }
149 : :
150 [ # # ]: 0 : g_clear_object (&self->dbus_connection);
151 : 0 : g_clear_error (&self->permission_error);
152 [ # # ]: 0 : g_clear_object (&self->cancellable);
153 : :
154 : 0 : G_OBJECT_CLASS (mct_application_parent_class)->dispose (object);
155 : 0 : }
156 : :
157 : : static GtkWindow *
158 : 0 : mct_application_get_main_window (MctApplication *self)
159 : : {
160 : 0 : return gtk_application_get_active_window (GTK_APPLICATION (self));
161 : : }
162 : :
163 : : static void
164 : 0 : mct_application_activate (GApplication *application)
165 : : {
166 : 0 : MctApplication *self = MCT_APPLICATION (application);
167 : 0 : GtkWindow *window = NULL;
168 : :
169 : 0 : window = mct_application_get_main_window (self);
170 : :
171 [ # # ]: 0 : if (window == NULL)
172 : : {
173 [ # # ]: 0 : g_autoptr(GtkBuilder) builder = NULL;
174 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
175 : :
176 : : /* Ensure the types used in the UI are registered. */
177 : 0 : g_type_ensure (MCT_TYPE_USER_CONTROLS);
178 : 0 : g_type_ensure (MCT_TYPE_USER_SELECTOR);
179 : :
180 : : /* Start loading the permission */
181 : 0 : polkit_permission_new ("org.freedesktop.MalcontentControl.administration",
182 : : NULL, self->cancellable,
183 : : permission_new_cb, self);
184 : :
185 : 0 : builder = gtk_builder_new ();
186 : :
187 : 0 : g_assert (self->dbus_connection == NULL);
188 : 0 : self->dbus_connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, self->cancellable, &local_error);
189 [ # # ]: 0 : if (self->dbus_connection == NULL)
190 : : {
191 : 0 : g_error ("Error getting system bus: %s", local_error->message);
192 : : return;
193 : : }
194 : :
195 : 0 : g_assert (self->user_manager == NULL);
196 : 0 : self->user_manager = g_object_ref (act_user_manager_get_default ());
197 : :
198 : 0 : gtk_builder_set_translation_domain (builder, "malcontent");
199 : 0 : gtk_builder_expose_object (builder, "user_manager", G_OBJECT (self->user_manager));
200 : 0 : gtk_builder_expose_object (builder, "dbus_connection", G_OBJECT (self->dbus_connection));
201 : :
202 : 0 : gtk_builder_add_from_resource (builder, "/org/freedesktop/MalcontentControl/ui/main.ui", &local_error);
203 : 0 : g_assert (local_error == NULL);
204 : :
205 : : /* Set up the main window. */
206 : 0 : window = GTK_WINDOW (gtk_builder_get_object (builder, "main_window"));
207 : 0 : gtk_window_set_application (window, GTK_APPLICATION (application));
208 : :
209 : 0 : self->main_stack = GTK_STACK (gtk_builder_get_object (builder, "main_stack"));
210 : 0 : self->user_selector = MCT_USER_SELECTOR (gtk_builder_get_object (builder, "user_selector"));
211 : 0 : self->user_controls = MCT_USER_CONTROLS (gtk_builder_get_object (builder, "user_controls"));
212 : 0 : self->error_title = GTK_LABEL (gtk_builder_get_object (builder, "error_title"));
213 : 0 : self->error_message = GTK_LABEL (gtk_builder_get_object (builder, "error_message"));
214 : 0 : self->lock_button = GTK_LOCK_BUTTON (gtk_builder_get_object (builder, "lock_button"));
215 : 0 : self->user_accounts_panel_button = GTK_BUTTON (gtk_builder_get_object (builder, "user_accounts_panel_button"));
216 : 0 : self->help_label = GTK_LABEL (gtk_builder_get_object (builder, "help_label"));
217 : :
218 : : /* Connect signals. */
219 : 0 : g_signal_connect_object (self->user_selector, "notify::user",
220 : : G_CALLBACK (user_selector_notify_user_cb),
221 : : self, 0 /* flags */);
222 : 0 : g_signal_connect_object (self->user_accounts_panel_button, "clicked",
223 : : G_CALLBACK (user_accounts_panel_button_clicked_cb),
224 : : self, 0 /* flags */);
225 : 0 : g_signal_connect (self->user_manager, "notify::is-loaded",
226 : : G_CALLBACK (user_manager_notify_is_loaded_cb), self);
227 : :
228 : : /* Work out whether to show the loading page or the main page, and show
229 : : * the controls for the initially selected user. */
230 : 0 : user_selector_notify_user_cb (G_OBJECT (self->user_selector), NULL, self);
231 : 0 : user_manager_notify_is_loaded_cb (G_OBJECT (self->user_manager), NULL, self);
232 : :
233 : 0 : gtk_widget_show (GTK_WIDGET (window));
234 : : }
235 : :
236 : : /* Bring the window to the front. */
237 : 0 : gtk_window_present (window);
238 : : }
239 : :
240 : : static void
241 : 0 : mct_application_startup (GApplication *application)
242 : : {
243 : 0 : const GActionEntry app_entries[] =
244 : : {
245 : : { "about", about_action_cb, NULL, NULL, NULL, { 0, } },
246 : : { "help", help_action_cb, NULL, NULL, NULL, { 0, } },
247 : : { "quit", quit_action_cb, NULL, NULL, NULL, { 0, } },
248 : : };
249 : :
250 : : /* Chain up. */
251 : 0 : G_APPLICATION_CLASS (mct_application_parent_class)->startup (application);
252 : :
253 : 0 : g_action_map_add_action_entries (G_ACTION_MAP (application), app_entries,
254 : : G_N_ELEMENTS (app_entries), application);
255 : :
256 : 0 : gtk_application_set_accels_for_action (GTK_APPLICATION (application),
257 : 0 : "app.help", (const gchar * const[]) { "F1", NULL });
258 : 0 : gtk_application_set_accels_for_action (GTK_APPLICATION (application),
259 : 0 : "app.quit", (const gchar * const[]) { "<Primary>q", "<Primary>w", NULL });
260 : 0 : }
261 : :
262 : : static gint
263 : 0 : mct_application_command_line (GApplication *application,
264 : : GApplicationCommandLine *command_line)
265 : : {
266 : 0 : MctApplication *self = MCT_APPLICATION (application);
267 : 0 : GVariantDict *options = g_application_command_line_get_options_dict (command_line);
268 : : const gchar *username;
269 : :
270 : : /* Show the application. */
271 : 0 : g_application_activate (application);
272 : :
273 : : /* Select a user if requested. */
274 [ # # # # ]: 0 : if (g_variant_dict_lookup (options, "user", "&s", &username) &&
275 : 0 : !mct_user_selector_select_user_by_username (self->user_selector, username))
276 : 0 : g_warning ("Failed to select user ‘%s’", username);
277 : :
278 : 0 : return 0; /* exit status */
279 : : }
280 : :
281 : : static void
282 : 0 : mct_application_class_init (MctApplicationClass *klass)
283 : : {
284 : 0 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
285 : 0 : GApplicationClass *application_class = G_APPLICATION_CLASS (klass);
286 : :
287 : 0 : object_class->constructed = mct_application_constructed;
288 : 0 : object_class->dispose = mct_application_dispose;
289 : :
290 : 0 : application_class->activate = mct_application_activate;
291 : 0 : application_class->startup = mct_application_startup;
292 : 0 : application_class->command_line = mct_application_command_line;
293 : 0 : }
294 : :
295 : : static void
296 : 0 : about_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
297 : : {
298 : 0 : MctApplication *self = MCT_APPLICATION (user_data);
299 : 0 : const gchar *authors[] =
300 : : {
301 : : "Philip Withnall <withnall@endlessm.com>",
302 : : "Georges Basile Stavracas Neto <georges@endlessm.com>",
303 : : "Andre Moreira Magalhaes <andre@endlessm.com>",
304 : : NULL
305 : : };
306 : :
307 : 0 : gtk_show_about_dialog (mct_application_get_main_window (self),
308 : : "version", VERSION,
309 : 0 : "copyright", _("Copyright © 2019, 2020 Endless Mobile, Inc."),
310 : : "authors", authors,
311 : : /* Translators: this should be "translated" to the
312 : : names of people who have translated Malcontent into
313 : : this language, one per line. */
314 : 0 : "translator-credits", _("translator-credits"),
315 : : "logo-icon-name", "org.freedesktop.MalcontentControl",
316 : : "license-type", GTK_LICENSE_GPL_2_0,
317 : : "wrap-license", TRUE,
318 : : /* Translators: "Malcontent" is the brand name of this
319 : : project, so should not be translated. */
320 : 0 : "website-label", _("Malcontent Website"),
321 : : "website", "https://gitlab.freedesktop.org/pwithnall/malcontent",
322 : : NULL);
323 : 0 : }
324 : :
325 : : static void
326 : 0 : help_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
327 : : {
328 : 0 : MctApplication *self = MCT_APPLICATION (user_data);
329 : 0 : g_autoptr(GError) local_error = NULL;
330 : :
331 [ # # ]: 0 : if (!gtk_show_uri_on_window (mct_application_get_main_window (self), "help:malcontent",
332 : : gtk_get_current_event_time (), &local_error))
333 : : {
334 : 0 : GtkWidget *dialog = gtk_message_dialog_new (mct_application_get_main_window (self),
335 : : GTK_DIALOG_MODAL,
336 : : GTK_MESSAGE_ERROR,
337 : : GTK_BUTTONS_OK,
338 : : _("The help contents could not be displayed"));
339 : 0 : gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", local_error->message);
340 : :
341 : 0 : gtk_dialog_run (GTK_DIALOG (dialog));
342 : :
343 : 0 : gtk_widget_destroy (dialog);
344 : : }
345 : 0 : }
346 : :
347 : : static void
348 : 0 : quit_action_cb (GSimpleAction *action, GVariant *parameters, gpointer user_data)
349 : : {
350 : 0 : MctApplication *self = MCT_APPLICATION (user_data);
351 : :
352 : 0 : g_application_quit (G_APPLICATION (self));
353 : 0 : }
354 : :
355 : : static void
356 : 0 : update_main_stack (MctApplication *self)
357 : : {
358 : : gboolean is_user_manager_loaded, is_permission_loaded, has_permission;
359 : : const gchar *new_page_name, *old_page_name;
360 : : GtkWidget *new_focus_widget;
361 : : ActUser *selected_user;
362 : :
363 : : /* The implementation of #ActUserManager guarantees that once is-loaded is
364 : : * true, it is never reset to false. */
365 : 0 : g_object_get (self->user_manager, "is-loaded", &is_user_manager_loaded, NULL);
366 [ # # # # ]: 0 : is_permission_loaded = (self->permission != NULL || self->permission_error != NULL);
367 [ # # # # ]: 0 : has_permission = (self->permission != NULL && g_permission_get_allowed (self->permission));
368 : 0 : selected_user = mct_user_selector_get_user (self->user_selector);
369 : :
370 : : /* Handle any loading errors (including those from getting the permission). */
371 [ # # # # ]: 0 : if ((is_user_manager_loaded && act_user_manager_no_service (self->user_manager)) ||
372 [ # # ]: 0 : self->permission_error != NULL)
373 : : {
374 : 0 : gtk_label_set_label (self->error_title,
375 : : _("Failed to load user data from the system"));
376 : 0 : gtk_label_set_label (self->error_message,
377 : : _("Please make sure that the AccountsService is installed and enabled."));
378 : :
379 : 0 : new_page_name = "error";
380 : 0 : new_focus_widget = NULL;
381 : : }
382 [ # # # # ]: 0 : else if (is_user_manager_loaded && selected_user == NULL)
383 : : {
384 : 0 : new_page_name = "no-other-users";
385 : 0 : new_focus_widget = GTK_WIDGET (self->user_accounts_panel_button);
386 : : }
387 [ # # # # ]: 0 : else if (is_permission_loaded && !has_permission)
388 : : {
389 : 0 : gtk_lock_button_set_permission (self->lock_button, self->permission);
390 : 0 : mct_user_controls_set_permission (self->user_controls, self->permission);
391 : :
392 : 0 : new_page_name = "unlock";
393 : 0 : new_focus_widget = GTK_WIDGET (self->lock_button);
394 : : }
395 [ # # # # ]: 0 : else if (is_permission_loaded && is_user_manager_loaded)
396 : 0 : {
397 : 0 : g_autofree gchar *help_label = NULL;
398 : :
399 : : /* Translators: Replace the link to commonsensemedia.org with some
400 : : * localised guidance for parents/carers on how to set restrictions on
401 : : * their child/caree in a responsible way which is in keeping with the
402 : : * best practice and culture of the region. If no suitable localised
403 : : * guidance exists, and if the default commonsensemedia.org link is not
404 : : * suitable, please file an issue against malcontent so we can discuss
405 : : * further!
406 : : * https://gitlab.freedesktop.org/pwithnall/malcontent/-/issues/new
407 : : */
408 : 0 : help_label = g_strdup_printf (_("It’s recommended that restrictions are "
409 : : "set as part of an ongoing conversation "
410 : : "with %s. <a href='https://www.commonsensemedia.org/privacy-and-internet-safety'>"
411 : : "Read guidance</a> on what to consider."),
412 : : act_user_get_real_name (selected_user));
413 : 0 : gtk_label_set_markup (self->help_label, help_label);
414 : :
415 : 0 : mct_user_controls_set_user (self->user_controls, selected_user);
416 : :
417 : 0 : new_page_name = "controls";
418 : 0 : new_focus_widget = GTK_WIDGET (self->user_controls);
419 : : }
420 : : else
421 : : {
422 : 0 : new_page_name = "loading";
423 : 0 : new_focus_widget = NULL;
424 : : }
425 : :
426 : 0 : old_page_name = gtk_stack_get_visible_child_name (self->main_stack);
427 : 0 : gtk_stack_set_visible_child_name (self->main_stack, new_page_name);
428 : :
429 [ # # # # ]: 0 : if (new_focus_widget != NULL && !g_str_equal (old_page_name, new_page_name))
430 : : {
431 [ # # ]: 0 : if (gtk_widget_get_can_focus (new_focus_widget))
432 : 0 : gtk_widget_grab_focus (new_focus_widget);
433 : : else
434 : 0 : gtk_widget_child_focus (new_focus_widget, GTK_DIR_TAB_FORWARD);
435 : : }
436 : 0 : }
437 : :
438 : : static void
439 : 0 : user_selector_notify_user_cb (GObject *obj,
440 : : GParamSpec *pspec,
441 : : gpointer user_data)
442 : : {
443 : 0 : MctApplication *self = MCT_APPLICATION (user_data);
444 : :
445 : 0 : update_main_stack (self);
446 : 0 : }
447 : :
448 : : static void
449 : 0 : user_manager_notify_is_loaded_cb (GObject *obj,
450 : : GParamSpec *pspec,
451 : : gpointer user_data)
452 : : {
453 : 0 : MctApplication *self = MCT_APPLICATION (user_data);
454 : :
455 : 0 : update_main_stack (self);
456 : 0 : }
457 : :
458 : : static void
459 : 0 : permission_new_cb (GObject *source_object,
460 : : GAsyncResult *result,
461 : : gpointer user_data)
462 : : {
463 : 0 : MctApplication *self = MCT_APPLICATION (user_data);
464 : 0 : g_autoptr(GPermission) permission = NULL;
465 : 0 : g_autoptr(GError) local_error = NULL;
466 : :
467 : 0 : permission = polkit_permission_new_finish (result, &local_error);
468 [ # # ]: 0 : if (permission == NULL)
469 : : {
470 : 0 : g_assert (self->permission_error == NULL);
471 : 0 : self->permission_error = g_steal_pointer (&local_error);
472 : 0 : g_debug ("Error getting permission: %s", self->permission_error->message);
473 : : }
474 : : else
475 : : {
476 : 0 : g_assert (self->permission == NULL);
477 : 0 : self->permission = g_steal_pointer (&permission);
478 : :
479 : 0 : g_signal_connect (self->permission, "notify::allowed",
480 : : G_CALLBACK (permission_notify_allowed_cb), self);
481 : : }
482 : :
483 : : /* Recalculate the UI. */
484 : 0 : update_main_stack (self);
485 : 0 : }
486 : :
487 : : static void
488 : 0 : permission_notify_allowed_cb (GObject *obj,
489 : : GParamSpec *pspec,
490 : : gpointer user_data)
491 : : {
492 : 0 : MctApplication *self = MCT_APPLICATION (user_data);
493 : :
494 : 0 : update_main_stack (self);
495 : 0 : }
496 : :
497 : : static void
498 : 0 : user_accounts_panel_button_clicked_cb (GtkButton *button,
499 : : gpointer user_data)
500 : : {
501 [ # # ]: 0 : g_autoptr(GError) local_error = NULL;
502 : :
503 [ # # ]: 0 : if (!g_spawn_command_line_async ("gnome-control-center user-accounts", &local_error))
504 : : {
505 : 0 : g_warning ("Error opening GNOME Control Center: %s",
506 : : local_error->message);
507 : 0 : return;
508 : : }
509 : : }
510 : :
511 : : /**
512 : : * mct_application_new:
513 : : *
514 : : * Create a new #MctApplication.
515 : : *
516 : : * Returns: (transfer full): a new #MctApplication
517 : : * Since: 0.5.0
518 : : */
519 : : MctApplication *
520 : 0 : mct_application_new (void)
521 : : {
522 : 0 : return g_object_new (MCT_TYPE_APPLICATION, NULL);
523 : : }
|