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