Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright © 2020 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 <act/act.h>
25 : : #include <gio/gio.h>
26 : : #include <glib.h>
27 : : #include <glib-object.h>
28 : : #include <glib/gi18n.h>
29 : : #include <gtk/gtk.h>
30 : :
31 : : #include "user-image.h"
32 : : #include "user-selector.h"
33 : :
34 : :
35 : : static void reload_users (MctUserSelector *self,
36 : : ActUser *selected_user);
37 : : static void notify_n_items_cb (GObject *obj,
38 : : GParamSpec *pspec,
39 : : gpointer user_data);
40 : : static void notify_is_loaded_cb (GObject *obj,
41 : : GParamSpec *pspec,
42 : : gpointer user_data);
43 : : static void user_added_cb (ActUserManager *user_manager,
44 : : ActUser *user,
45 : : gpointer user_data);
46 : : static void user_changed_or_removed_cb (ActUserManager *user_manager,
47 : : ActUser *user,
48 : : gpointer user_data);
49 : : static void on_user_row_activated (MctUserSelector *self,
50 : : AdwActionRow *row);
51 : : static GtkWidget *create_user_row (gpointer item, gpointer user_data);
52 : :
53 : :
54 : : /**
55 : : * MctUserSelector:
56 : : *
57 : : * The user selector is a widget which lists available user accounts and allows
58 : : * the user to select one.
59 : : *
60 : : * Since: 0.5.0
61 : : */
62 : : struct _MctUserSelector
63 : : {
64 : : GtkBox parent_instance;
65 : :
66 : : GtkListBox *user_list;
67 : :
68 : : GListStore *model /* (owned) */;
69 : :
70 : : ActUserManager *user_manager; /* (owned) */
71 : : ActUser *user; /* (owned) */
72 : : gboolean show_administrators;
73 : : guint n_users;
74 : : };
75 : :
76 [ # # # # : 0 : G_DEFINE_TYPE (MctUserSelector, mct_user_selector, GTK_TYPE_BOX)
# # ]
77 : :
78 : : typedef enum
79 : : {
80 : : PROP_USER = 1,
81 : : PROP_USER_MANAGER,
82 : : PROP_N_USERS,
83 : : PROP_SHOW_ADMINISTRATORS,
84 : : } MctUserSelectorProperty;
85 : :
86 : : static GParamSpec *properties[PROP_SHOW_ADMINISTRATORS + 1];
87 : :
88 : : static void
89 : 0 : mct_user_selector_constructed (GObject *obj)
90 : : {
91 : 0 : MctUserSelector *self = MCT_USER_SELECTOR (obj);
92 : :
93 : 0 : g_assert (self->user_manager != NULL);
94 : :
95 : 0 : self->model = g_list_store_new (ACT_TYPE_USER);
96 : 0 : gtk_list_box_bind_model (self->user_list,
97 : 0 : G_LIST_MODEL (self->model),
98 : : (GtkListBoxCreateWidgetFunc)create_user_row,
99 : : self,
100 : : NULL);
101 : :
102 : 0 : g_signal_connect_object (self->model, "notify::n-items",
103 : : G_CALLBACK (notify_n_items_cb),
104 : : self, 0);
105 : 0 : g_signal_connect (self->user_manager, "user-changed",
106 : : G_CALLBACK (user_changed_or_removed_cb), self);
107 : 0 : g_signal_connect (self->user_manager, "user-is-logged-in-changed",
108 : : G_CALLBACK (user_changed_or_removed_cb), self);
109 : 0 : g_signal_connect (self->user_manager, "user-added",
110 : : G_CALLBACK (user_added_cb), self);
111 : 0 : g_signal_connect (self->user_manager, "user-removed",
112 : : G_CALLBACK (user_changed_or_removed_cb), self);
113 : 0 : g_signal_connect (self->user_manager, "notify::is-loaded",
114 : : G_CALLBACK (notify_is_loaded_cb), self);
115 : :
116 : : /* Start loading the user accounts. */
117 : 0 : notify_is_loaded_cb (G_OBJECT (self->user_manager), NULL, self);
118 : :
119 : 0 : G_OBJECT_CLASS (mct_user_selector_parent_class)->constructed (obj);
120 : 0 : }
121 : :
122 : : static void
123 : 0 : mct_user_selector_get_property (GObject *object,
124 : : guint prop_id,
125 : : GValue *value,
126 : : GParamSpec *pspec)
127 : : {
128 : 0 : MctUserSelector *self = MCT_USER_SELECTOR (object);
129 : :
130 [ # # # # : 0 : switch ((MctUserSelectorProperty) prop_id)
# ]
131 : : {
132 : 0 : case PROP_USER:
133 : 0 : g_value_set_object (value, self->user);
134 : 0 : break;
135 : :
136 : 0 : case PROP_USER_MANAGER:
137 : 0 : g_value_set_object (value, self->user_manager);
138 : 0 : break;
139 : :
140 : 0 : case PROP_N_USERS:
141 : 0 : g_value_set_uint (value, mct_user_selector_get_n_users (self));
142 : 0 : break;
143 : :
144 : 0 : case PROP_SHOW_ADMINISTRATORS:
145 : 0 : g_value_set_boolean (value, self->show_administrators);
146 : 0 : break;
147 : :
148 : 0 : default:
149 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
150 : : }
151 : 0 : }
152 : :
153 : : static void
154 : 0 : mct_user_selector_set_property (GObject *object,
155 : : guint prop_id,
156 : : const GValue *value,
157 : : GParamSpec *pspec)
158 : : {
159 : 0 : MctUserSelector *self = MCT_USER_SELECTOR (object);
160 : :
161 [ # # # # : 0 : switch ((MctUserSelectorProperty) prop_id)
# ]
162 : : {
163 : 0 : case PROP_USER:
164 : : /* Currently read only */
165 : : g_assert_not_reached ();
166 : : break;
167 : :
168 : 0 : case PROP_USER_MANAGER:
169 : 0 : g_assert (self->user_manager == NULL);
170 : 0 : self->user_manager = g_value_dup_object (value);
171 : 0 : break;
172 : :
173 : 0 : case PROP_N_USERS:
174 : : /* Currently read only */
175 : : g_assert_not_reached ();
176 : : break;
177 : :
178 : 0 : case PROP_SHOW_ADMINISTRATORS:
179 : 0 : self->show_administrators = g_value_get_boolean (value);
180 : 0 : reload_users (self, NULL);
181 : 0 : break;
182 : :
183 : 0 : default:
184 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
185 : : }
186 : 0 : }
187 : :
188 : : static void
189 : 0 : mct_user_selector_dispose (GObject *object)
190 : : {
191 : 0 : MctUserSelector *self = (MctUserSelector *)object;
192 : :
193 [ # # ]: 0 : g_clear_object (&self->model);
194 [ # # ]: 0 : g_clear_object (&self->user);
195 : :
196 [ # # ]: 0 : if (self->user_manager != NULL)
197 : : {
198 : 0 : g_signal_handlers_disconnect_by_func (self->user_manager, notify_is_loaded_cb, self);
199 : 0 : g_signal_handlers_disconnect_by_func (self->user_manager, user_changed_or_removed_cb, self);
200 : 0 : g_signal_handlers_disconnect_by_func (self->user_manager, user_added_cb, self);
201 : :
202 [ # # ]: 0 : g_clear_object (&self->user_manager);
203 : : }
204 : :
205 : 0 : G_OBJECT_CLASS (mct_user_selector_parent_class)->dispose (object);
206 : 0 : }
207 : :
208 : : static void
209 : 0 : mct_user_selector_class_init (MctUserSelectorClass *klass)
210 : : {
211 : 0 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
212 : 0 : GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
213 : :
214 : 0 : object_class->constructed = mct_user_selector_constructed;
215 : 0 : object_class->get_property = mct_user_selector_get_property;
216 : 0 : object_class->set_property = mct_user_selector_set_property;
217 : 0 : object_class->dispose = mct_user_selector_dispose;
218 : :
219 : : /**
220 : : * MctUserSelector:user: (nullable)
221 : : *
222 : : * The currently selected user account, or %NULL if no user is selected.
223 : : * Currently read only but may become writable in future.
224 : : *
225 : : * Since: 0.5.0
226 : : */
227 : 0 : properties[PROP_USER] =
228 : 0 : g_param_spec_object ("user",
229 : : "User",
230 : : "The currently selected user account, or %NULL if no user is selected.",
231 : : ACT_TYPE_USER,
232 : : G_PARAM_READABLE |
233 : : G_PARAM_STATIC_STRINGS |
234 : : G_PARAM_EXPLICIT_NOTIFY);
235 : :
236 : : /**
237 : : * MctUserSelector:user-manager: (not nullable)
238 : : *
239 : : * The user manager providing the data for the widget.
240 : : *
241 : : * Since: 0.5.0
242 : : */
243 : 0 : properties[PROP_USER_MANAGER] =
244 : 0 : g_param_spec_object ("user-manager",
245 : : "User Manager",
246 : : "The user manager providing the data for the widget.",
247 : : ACT_TYPE_USER_MANAGER,
248 : : G_PARAM_READWRITE |
249 : : G_PARAM_CONSTRUCT_ONLY |
250 : : G_PARAM_STATIC_STRINGS |
251 : : G_PARAM_EXPLICIT_NOTIFY);
252 : :
253 : : /**
254 : : * MctUserSelector:show-administrators:
255 : : *
256 : : * Whether to show administrators in the list, or hide them.
257 : : *
258 : : * Since: 0.5.0
259 : : */
260 : 0 : properties[PROP_SHOW_ADMINISTRATORS] =
261 : 0 : g_param_spec_boolean ("show-administrators",
262 : : "Show Administrators?",
263 : : "Whether to show administrators in the list, or hide them.",
264 : : TRUE,
265 : : G_PARAM_READWRITE |
266 : : G_PARAM_STATIC_STRINGS);
267 : :
268 : : /**
269 : : * MctUserSelector:n-users:
270 : : *
271 : : * The number of users contained in this selector.
272 : : *
273 : : * Since: 0.14.0
274 : : */
275 : 0 : properties[PROP_N_USERS] =
276 : 0 : g_param_spec_uint ("n-users",
277 : : NULL,
278 : : NULL,
279 : : 0,
280 : : G_MAXUINT,
281 : : 0,
282 : : G_PARAM_READABLE |
283 : : G_PARAM_STATIC_STRINGS |
284 : : G_PARAM_EXPLICIT_NOTIFY);
285 : :
286 : 0 : g_object_class_install_properties (object_class, G_N_ELEMENTS (properties), properties);
287 : :
288 : 0 : gtk_widget_class_set_template_from_resource (widget_class, "/org/freedesktop/MalcontentControl/ui/user-selector.ui");
289 : :
290 : 0 : gtk_widget_class_bind_template_child (widget_class, MctUserSelector, user_list);
291 : :
292 : 0 : gtk_widget_class_bind_template_callback (widget_class, on_user_row_activated);
293 : 0 : }
294 : :
295 : : static void
296 : 0 : mct_user_selector_init (MctUserSelector *self)
297 : : {
298 : 0 : self->show_administrators = TRUE;
299 : :
300 : 0 : gtk_widget_init_template (GTK_WIDGET (self));
301 : 0 : }
302 : :
303 : : static void
304 : 0 : notify_n_items_cb (GObject *obj,
305 : : GParamSpec *pspec,
306 : : gpointer user_data)
307 : : {
308 : 0 : MctUserSelector *self = MCT_USER_SELECTOR (user_data);
309 : :
310 : 0 : g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_N_USERS]);
311 : 0 : }
312 : :
313 : : static void
314 : 0 : notify_is_loaded_cb (GObject *obj,
315 : : GParamSpec *pspec,
316 : : gpointer user_data)
317 : : {
318 : 0 : MctUserSelector *self = MCT_USER_SELECTOR (user_data);
319 : : gboolean is_loaded;
320 : :
321 : : /* The implementation of #ActUserManager guarantees that once is-loaded is
322 : : * true, it is never reset to false. */
323 : 0 : g_object_get (self->user_manager, "is-loaded", &is_loaded, NULL);
324 [ # # ]: 0 : if (is_loaded)
325 : 0 : reload_users (self, NULL);
326 : 0 : }
327 : :
328 : : static const gchar *
329 : 0 : get_real_or_user_name (ActUser *user)
330 : : {
331 : : const gchar *name;
332 : :
333 : 0 : name = act_user_get_real_name (user);
334 [ # # ]: 0 : if (name == NULL)
335 : 0 : name = act_user_get_user_name (user);
336 : :
337 : 0 : return name;
338 : : }
339 : :
340 : : static void
341 : 0 : on_user_row_activated (MctUserSelector *self,
342 : : AdwActionRow *row)
343 : : {
344 : : uid_t uid;
345 : 0 : ActUser *user = NULL;
346 : :
347 [ # # ]: 0 : g_clear_object (&self->user);
348 : :
349 : 0 : uid = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "uid"));
350 : 0 : user = act_user_manager_get_user_by_id (self->user_manager, uid);
351 : :
352 [ # # ]: 0 : if (g_set_object (&self->user, user))
353 : 0 : g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER]);
354 : 0 : }
355 : :
356 : : static GtkWidget *
357 : 0 : create_user_row (gpointer item, gpointer user_data)
358 : : {
359 : : ActUser *user;
360 : : GtkWidget *row, *user_image;
361 : :
362 : 0 : row = adw_action_row_new ();
363 : 0 : gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), TRUE);
364 : :
365 : 0 : user = item;
366 : :
367 : 0 : g_object_set_data (G_OBJECT (row), "uid", GINT_TO_POINTER (act_user_get_uid (user)));
368 : :
369 : 0 : adw_preferences_row_set_title (ADW_PREFERENCES_ROW (row),
370 : 0 : get_real_or_user_name (user));
371 : :
372 : 0 : user_image = mct_user_image_new ();
373 : 0 : mct_user_image_set_user (MCT_USER_IMAGE (user_image), user);
374 : 0 : gtk_widget_set_margin_top (user_image, 12);
375 : 0 : gtk_widget_set_margin_bottom (user_image, 12);
376 : 0 : adw_action_row_add_prefix (ADW_ACTION_ROW (row), user_image);
377 : 0 : adw_action_row_activate (ADW_ACTION_ROW (row));
378 : :
379 : 0 : GtkWidget *arrow = gtk_image_new_from_icon_name ("go-next-symbolic");
380 : 0 : adw_action_row_add_suffix (ADW_ACTION_ROW (row), arrow);
381 : :
382 : 0 : return row;
383 : : }
384 : :
385 : : static gint
386 : 0 : sort_users (gconstpointer a, gconstpointer b, gpointer user_data)
387 : : {
388 : : ActUser *ua, *ub;
389 : : gint result;
390 : :
391 : 0 : ua = ACT_USER ((gpointer) a);
392 : 0 : ub = ACT_USER ((gpointer) b);
393 : :
394 : : /* Make sure the current user is shown first */
395 [ # # ]: 0 : if (act_user_get_uid (ua) == getuid ())
396 : : {
397 : 0 : result = G_MININT32;
398 : : }
399 [ # # ]: 0 : else if (act_user_get_uid (ub) == getuid ())
400 : : {
401 : 0 : result = G_MAXINT32;
402 : : }
403 : : else
404 : : {
405 : 0 : g_autofree gchar *name1 = NULL, *name2 = NULL;
406 : :
407 : 0 : name1 = g_utf8_collate_key (get_real_or_user_name (ua), -1);
408 : 0 : name2 = g_utf8_collate_key (get_real_or_user_name (ub), -1);
409 : :
410 : 0 : result = strcmp (name1, name2);
411 : : }
412 : :
413 : 0 : return result;
414 : : }
415 : :
416 : : static void
417 : 0 : reload_users (MctUserSelector *self,
418 : : ActUser *selected_user)
419 : : {
420 : : ActUser *user;
421 : 0 : g_autoptr(GSList) list = NULL;
422 : : GSList *l;
423 : :
424 : 0 : list = act_user_manager_list_users (self->user_manager);
425 : 0 : g_debug ("Got %u users", g_slist_length (list));
426 : :
427 : 0 : g_list_store_remove_all (self->model);
428 : :
429 [ # # ]: 0 : for (l = list; l; l = l->next)
430 : : {
431 : 0 : user = l->data;
432 : :
433 [ # # ]: 0 : if (act_user_get_account_type (user) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR &&
434 [ # # ]: 0 : !self->show_administrators)
435 : : {
436 : 0 : g_debug ("Ignoring administrator %s", get_real_or_user_name (user));
437 : 0 : continue;
438 : : }
439 : :
440 : 0 : g_debug ("Adding user %s", get_real_or_user_name (user));
441 : 0 : user_added_cb (self->user_manager, user, self);
442 : : }
443 : 0 : }
444 : :
445 : : static void
446 : 0 : user_added_cb (ActUserManager *user_manager,
447 : : ActUser *user,
448 : : gpointer user_data)
449 : : {
450 : 0 : MctUserSelector *self = MCT_USER_SELECTOR (user_data);
451 : :
452 [ # # # # ]: 0 : if (act_user_is_system_account (user) ||
453 : 0 : (act_user_get_account_type (user) == ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR &&
454 [ # # ]: 0 : !self->show_administrators))
455 : 0 : return;
456 : :
457 : 0 : g_debug ("User added: %u %s", (guint) act_user_get_uid (user), get_real_or_user_name (user));
458 : :
459 : 0 : g_list_store_insert_sorted (self->model, user, sort_users, self);
460 : : }
461 : :
462 : : static void
463 : 0 : user_changed_or_removed_cb (ActUserManager *user_manager,
464 : : ActUser *user,
465 : : gpointer user_data)
466 : : {
467 : 0 : MctUserSelector *self = MCT_USER_SELECTOR (user_data);
468 : :
469 : 0 : reload_users (self, self->user);
470 : 0 : }
471 : :
472 : : /**
473 : : * mct_user_selector_new:
474 : : * @user_manager: (transfer none): an #ActUserManager to provide the user data
475 : : *
476 : : * Create a new #MctUserSelector widget.
477 : : *
478 : : * Returns: (transfer full): a new user selector
479 : : * Since: 0.5.0
480 : : */
481 : : MctUserSelector *
482 : 0 : mct_user_selector_new (ActUserManager *user_manager)
483 : : {
484 : 0 : g_return_val_if_fail (ACT_IS_USER_MANAGER (user_manager), NULL);
485 : :
486 : 0 : return g_object_new (MCT_TYPE_USER_SELECTOR,
487 : : "user-manager", user_manager,
488 : : NULL);
489 : : }
490 : :
491 : : /**
492 : : * mct_user_selector_get_user:
493 : : * @self: an #MctUserSelector
494 : : *
495 : : * Get the currently selected user, or %NULL if no user is selected.
496 : : *
497 : : * Returns: (transfer none) (nullable): the currently selected user
498 : : * Since: 0.5.0
499 : : */
500 : : ActUser *
501 : 0 : mct_user_selector_get_user (MctUserSelector *self)
502 : : {
503 : 0 : g_return_val_if_fail (MCT_IS_USER_SELECTOR (self), NULL);
504 : :
505 : 0 : return self->user;
506 : : }
507 : :
508 : : /**
509 : : * mct_user_selector_select_user_by_username:
510 : : * @self: an #MctUserSelector
511 : : * @username: username of the user to select
512 : : *
513 : : * Selects the given @username in the widget. This might fail if @username isn’t
514 : : * a valid user, or if they aren’t listed in the selector due to being an
515 : : * administrator (see #MctUserSelector:show-administrators).
516 : : *
517 : : * Returns: %TRUE if the user was successfully selected, %FALSE otherwise
518 : : * Since: 0.10.0
519 : : */
520 : : gboolean
521 : 0 : mct_user_selector_select_user_by_username (MctUserSelector *self,
522 : : const gchar *username)
523 : : {
524 : 0 : ActUser *user = NULL;
525 : :
526 : 0 : g_return_val_if_fail (MCT_IS_USER_SELECTOR (self), FALSE);
527 : 0 : g_return_val_if_fail (username != NULL && *username != '\0', FALSE);
528 : :
529 : 0 : user = act_user_manager_get_user (self->user_manager, username);
530 [ # # ]: 0 : if (user == NULL)
531 : 0 : return FALSE;
532 : :
533 [ # # ]: 0 : if (g_set_object (&self->user, user))
534 : 0 : g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_USER]);
535 : :
536 : 0 : return TRUE;
537 : : }
538 : :
539 : : /**
540 : : * mct_user_selector_get_n_users:
541 : : * @self: an #MctUserSelector
542 : : *
543 : : * Gets the number of users in @self.
544 : : *
545 : : * Returns: the number of users in @self
546 : : * Since: 0.14.0
547 : : */
548 : : guint
549 : 0 : mct_user_selector_get_n_users (MctUserSelector *self)
550 : : {
551 : 0 : g_return_val_if_fail (MCT_IS_USER_SELECTOR (self), 0);
552 : :
553 : 0 : return g_list_model_get_n_items (G_LIST_MODEL (self->model));
554 : : }
|