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