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