Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
2 : : *
3 : : * Copyright 2025 GNOME Foundation, Inc.
4 : : *
5 : : * SPDX-License-Identifier: LGPL-2.1-or-later
6 : : *
7 : : * This library is free software; you can redistribute it and/or
8 : : * modify it under the terms of the GNU Lesser General Public
9 : : * License as published by the Free Software Foundation; either
10 : : * version 2.1 of the License, or (at your option) any later version.
11 : : *
12 : : * This library 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 GNU
15 : : * Lesser General Public License for more details.
16 : : *
17 : : * You should have received a copy of the GNU Lesser General Public
18 : : * License along with this library; if not, write to the Free Software
19 : : * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 : : *
21 : : * Authors:
22 : : * - Philip Withnall <pwithnall@gnome.org>
23 : : */
24 : :
25 : : #include "config.h"
26 : :
27 : : #include <act/act.h>
28 : : #include <glib.h>
29 : : #include <glib-object.h>
30 : : #include <glib/gi18n-lib.h>
31 : : #include <gio/gio.h>
32 : : #include <libmalcontent/user.h>
33 : :
34 : : #include "enums.h"
35 : : #include "user-private.h"
36 : :
37 : :
38 : : /**
39 : : * MctUser:
40 : : *
41 : : * A user account on the system.
42 : : *
43 : : * Many of the properties on the account may change over time if the user
44 : : * database is edited. If so, [signal@GObject.Object::notify] will be emitted
45 : : * on them. [property@Malcontent.User:uid] and
46 : : * [property@Malcontent.User:username] cannot change after construction.
47 : : *
48 : : * Since: 0.14.0
49 : : */
50 : : struct _MctUser
51 : : {
52 : : GObject parent_instance;
53 : :
54 : : ActUser *user; /* (owned) (not nullable) */
55 : : unsigned long user_changed_id;
56 : : };
57 : :
58 [ + + + - : 13 : G_DEFINE_TYPE (MctUser, mct_user, G_TYPE_OBJECT)
+ + ]
59 : :
60 : : typedef enum
61 : : {
62 : : PROP_UID = 1,
63 : : PROP_USERNAME,
64 : : PROP_REAL_NAME,
65 : : PROP_DISPLAY_NAME,
66 : : PROP_USER_TYPE,
67 : : PROP_ICON_PATH,
68 : : PROP_LOGIN_TIME,
69 : : PROP_LOCALE,
70 : : } MctUserProperty;
71 : :
72 : : static GParamSpec *props[PROP_LOCALE + 1] = { NULL, };
73 : :
74 : : static void
75 : 0 : mct_user_init (MctUser *self)
76 : : {
77 : : /* Nothing to do here. */
78 : 0 : }
79 : :
80 : : static void
81 : 0 : mct_user_get_property (GObject *object,
82 : : guint property_id,
83 : : GValue *value,
84 : : GParamSpec *spec)
85 : : {
86 : 0 : MctUser *self = MCT_USER (object);
87 : :
88 [ # # # # : 0 : switch ((MctUserProperty) property_id)
# # # #
# ]
89 : : {
90 : 0 : case PROP_UID:
91 : 0 : g_value_set_uint (value, mct_user_get_uid (self));
92 : 0 : break;
93 : 0 : case PROP_USERNAME:
94 : 0 : g_value_set_string (value, mct_user_get_username (self));
95 : 0 : break;
96 : 0 : case PROP_REAL_NAME:
97 : 0 : g_value_set_string (value, mct_user_get_real_name (self));
98 : 0 : break;
99 : 0 : case PROP_DISPLAY_NAME:
100 : 0 : g_value_set_string (value, mct_user_get_display_name (self));
101 : 0 : break;
102 : 0 : case PROP_USER_TYPE:
103 : 0 : g_value_set_enum (value, mct_user_get_user_type (self));
104 : 0 : break;
105 : 0 : case PROP_ICON_PATH:
106 : 0 : g_value_set_string (value, mct_user_get_icon_path (self));
107 : 0 : break;
108 : 0 : case PROP_LOGIN_TIME:
109 : 0 : g_value_set_uint64 (value, mct_user_get_login_time (self));
110 : 0 : break;
111 : 0 : case PROP_LOCALE:
112 : 0 : g_value_set_string (value, mct_user_get_locale (self));
113 : 0 : break;
114 : 0 : default:
115 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
116 : 0 : break;
117 : : }
118 : 0 : }
119 : :
120 : : static void
121 : 0 : mct_user_set_property (GObject *object,
122 : : guint property_id,
123 : : const GValue *value,
124 : : GParamSpec *spec)
125 : : {
126 [ # # ]: 0 : switch ((MctUserProperty) property_id)
127 : : {
128 : 0 : case PROP_UID:
129 : : case PROP_USERNAME:
130 : : case PROP_REAL_NAME:
131 : : case PROP_DISPLAY_NAME:
132 : : case PROP_USER_TYPE:
133 : : case PROP_ICON_PATH:
134 : : case PROP_LOGIN_TIME:
135 : : case PROP_LOCALE:
136 : : /* Read-only. */
137 : : g_assert_not_reached ();
138 : : break;
139 : 0 : default:
140 : 0 : G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
141 : 0 : break;
142 : : }
143 : 0 : }
144 : :
145 : : static void
146 : 0 : mct_user_dispose (GObject *object)
147 : : {
148 : 0 : MctUser *self = MCT_USER (object);
149 : :
150 [ # # ]: 0 : g_clear_signal_handler (&self->user_changed_id, self->user);
151 [ # # ]: 0 : g_clear_object (&self->user);
152 : :
153 : 0 : G_OBJECT_CLASS (mct_user_parent_class)->dispose (object);
154 : 0 : }
155 : :
156 : : static void
157 : 1 : mct_user_class_init (MctUserClass *klass)
158 : : {
159 : 1 : GObjectClass *object_class = G_OBJECT_CLASS (klass);
160 : :
161 : 1 : object_class->dispose = mct_user_dispose;
162 : 1 : object_class->get_property = mct_user_get_property;
163 : 1 : object_class->set_property = mct_user_set_property;
164 : :
165 : : /**
166 : : * MctUser:uid:
167 : : *
168 : : * The user’s UID.
169 : : *
170 : : * This will not change after the [class@Malcontent.User] is constructed.
171 : : *
172 : : * Since: 0.14.0
173 : : */
174 : 1 : props[PROP_UID] = g_param_spec_uint ("uid",
175 : : NULL, NULL,
176 : : 0, G_MAXUINT, 0,
177 : : G_PARAM_READABLE |
178 : : G_PARAM_STATIC_STRINGS |
179 : : G_PARAM_EXPLICIT_NOTIFY);
180 : :
181 : : /**
182 : : * MctUser:username:
183 : : *
184 : : * The user’s username.
185 : : *
186 : : * This will not change after the [class@Malcontent.User] is constructed.
187 : : *
188 : : * Since: 0.14.0
189 : : */
190 : 1 : props[PROP_USERNAME] = g_param_spec_string ("username",
191 : : NULL, NULL,
192 : : "unknown",
193 : : G_PARAM_READABLE |
194 : : G_PARAM_STATIC_STRINGS |
195 : : G_PARAM_EXPLICIT_NOTIFY);
196 : :
197 : : /**
198 : : * MctUser:real-name: (nullable)
199 : : *
200 : : * The user’s real name.
201 : : *
202 : : * This may be `NULL` if not set on the system. If you need to use a
203 : : * non-`NULL` string for display purposes, see
204 : : * [property@Malcontent.User:display-name].
205 : : *
206 : : * Since: 0.14.0
207 : : */
208 : 1 : props[PROP_REAL_NAME] = g_param_spec_string ("real-name",
209 : : NULL, NULL,
210 : : "unknown",
211 : : G_PARAM_READABLE |
212 : : G_PARAM_STATIC_STRINGS |
213 : : G_PARAM_EXPLICIT_NOTIFY);
214 : :
215 : : /**
216 : : * MctUser:display-name: (not nullable)
217 : : *
218 : : * The user’s real name or username if the real name is unset.
219 : : *
220 : : * This is guaranteed to not be `NULL`, and should be used in user interfaces
221 : : * when you need a non-`NULl` human readable name for a user.
222 : : *
223 : : * Since: 0.14.0
224 : : */
225 : 1 : props[PROP_DISPLAY_NAME] = g_param_spec_string ("display-name",
226 : : NULL, NULL,
227 : : "unknown",
228 : : G_PARAM_READABLE |
229 : : G_PARAM_STATIC_STRINGS |
230 : : G_PARAM_EXPLICIT_NOTIFY);
231 : :
232 : : /**
233 : : * MctUser:user-type:
234 : : *
235 : : * Type of the user.
236 : : *
237 : : * Since: 0.14.0
238 : : */
239 : 1 : props[PROP_USER_TYPE] = g_param_spec_enum ("user-type",
240 : : NULL, NULL,
241 : : MCT_TYPE_USER_TYPE,
242 : : MCT_USER_TYPE_UNKNOWN,
243 : : G_PARAM_READABLE |
244 : : G_PARAM_STATIC_STRINGS |
245 : : G_PARAM_EXPLICIT_NOTIFY);
246 : :
247 : : /**
248 : : * MctUser:icon-path: (type filename) (nullable)
249 : : *
250 : : * Path to the user’s icon/avatar image.
251 : : *
252 : : * This may be `NULL` if not set on the system.
253 : : *
254 : : * Since: 0.14.0
255 : : */
256 : 1 : props[PROP_ICON_PATH] = g_param_spec_string ("icon-path",
257 : : NULL, NULL,
258 : : NULL,
259 : : G_PARAM_READABLE |
260 : : G_PARAM_STATIC_STRINGS |
261 : : G_PARAM_EXPLICIT_NOTIFY);
262 : :
263 : : /**
264 : : * MctUser:login-time:
265 : : *
266 : : * The last login time for this user, in seconds since the Unix epoch.
267 : : *
268 : : * If the user has never logged in, this will be zero.
269 : : *
270 : : * Since: 0.14.0
271 : : */
272 : 1 : props[PROP_LOGIN_TIME] = g_param_spec_uint64 ("login-time",
273 : : NULL, NULL,
274 : : 0, G_MAXUINT64, 0,
275 : : G_PARAM_READABLE |
276 : : G_PARAM_STATIC_STRINGS |
277 : : G_PARAM_EXPLICIT_NOTIFY);
278 : :
279 : :
280 : : /**
281 : : * MctUser:locale: (nullable)
282 : : *
283 : : * The user’s login locale.
284 : : *
285 : : * This is in the format `language[_territory][.codeset][@modifier]`, where
286 : : * `language` is an ISO 639 language code, `territory` is an ISO 3166 country
287 : : * code, and `codeset` is a character set or encoding identifier like
288 : : * `ISO-8859-1` or `UTF-8`; as specified by
289 : : * [`setlocale(3)`](man:setlocale(3)).
290 : : *
291 : : * This may be `NULL` if not set on the system. If it’s the empty string
292 : : * (`""`) then the user is using the system default locale.
293 : : *
294 : : * Since: 0.14.0
295 : : */
296 : 1 : props[PROP_LOCALE] = g_param_spec_string ("locale",
297 : : NULL, NULL,
298 : : NULL,
299 : : G_PARAM_READABLE |
300 : : G_PARAM_STATIC_STRINGS |
301 : : G_PARAM_EXPLICIT_NOTIFY);
302 : :
303 : 1 : g_object_class_install_properties (object_class,
304 : : G_N_ELEMENTS (props),
305 : : props);
306 : 1 : }
307 : :
308 : : static void
309 : 0 : user_changed_cb (ActUser *act_user,
310 : : void *user_data)
311 : : {
312 : 0 : MctUser *self = MCT_USER (user_data);
313 : :
314 : : /* Unfortunately accountssservice doesn’t give more detail about *what*
315 : : * changed, so notify for all the properties which could possibly change.
316 : : * UID and username cannot change. */
317 : 0 : g_object_freeze_notify (G_OBJECT (self));
318 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_REAL_NAME]);
319 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_DISPLAY_NAME]);
320 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_USER_TYPE]);
321 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_ICON_PATH]);
322 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LOGIN_TIME]);
323 : 0 : g_object_notify_by_pspec (G_OBJECT (self), props[PROP_LOCALE]);
324 : 0 : g_object_thaw_notify (G_OBJECT (self));
325 : 0 : }
326 : :
327 : : /**
328 : : * mct_user_new_from_act_user:
329 : : * @act_user: an AccountsService user
330 : : *
331 : : * Create a new [class@Malcontent.User] from the given @act_user.
332 : : *
333 : : * This is an internal constructor.
334 : : *
335 : : * Returns: (transfer full) (not nullable): a new user object
336 : : * Since: 0.14.0
337 : : */
338 : : MctUser *
339 : 0 : mct_user_new_from_act_user (ActUser *act_user)
340 : : {
341 : 0 : g_autoptr(MctUser) user = NULL;
342 : :
343 : 0 : g_return_val_if_fail (ACT_IS_USER (act_user), NULL);
344 : :
345 : 0 : user = g_object_new (MCT_TYPE_USER, NULL);
346 : 0 : user->user = g_object_ref (act_user);
347 : 0 : user->user_changed_id = g_signal_connect (user->user, "changed",
348 : : G_CALLBACK (user_changed_cb),
349 : : user);
350 : :
351 : 0 : return g_steal_pointer (&user);
352 : : }
353 : :
354 : : /**
355 : : * mct_user_get_uid:
356 : : * @self: a user
357 : : *
358 : : * Get the value of the [property@Malcontent.User:uid] property.
359 : : *
360 : : * Returns: the user’s ID
361 : : * Since: 0.14.0
362 : : */
363 : : uid_t
364 : 0 : mct_user_get_uid (MctUser *self)
365 : : {
366 : 0 : g_return_val_if_fail (MCT_IS_USER (self), (uid_t) -1);
367 : :
368 : 0 : return act_user_get_uid (self->user);
369 : : }
370 : :
371 : : /**
372 : : * mct_user_get_username:
373 : : * @self: a user
374 : : *
375 : : * Get the value of the [property@Malcontent.User:username] property.
376 : : *
377 : : * Returns: (not nullable): the user’s username
378 : : * Since: 0.14.0
379 : : */
380 : : const char *
381 : 0 : mct_user_get_username (MctUser *self)
382 : : {
383 : 0 : g_return_val_if_fail (MCT_IS_USER (self), "unknown");
384 : :
385 : 0 : return act_user_get_user_name (self->user);
386 : : }
387 : :
388 : : /**
389 : : * mct_user_get_real_name:
390 : : * @self: a user
391 : : *
392 : : * Get the value of the [property@Malcontent.User:real-name] property.
393 : : *
394 : : * Returns: (nullable): the user’s real name, or `NULL` if not set
395 : : * Since: 0.14.0
396 : : */
397 : : const char *
398 : 0 : mct_user_get_real_name (MctUser *self)
399 : : {
400 : 0 : g_return_val_if_fail (MCT_IS_USER (self), NULL);
401 : :
402 : 0 : return act_user_get_real_name (self->user);
403 : : }
404 : :
405 : : /**
406 : : * mct_user_get_display_name:
407 : : * @self: a user
408 : : *
409 : : * Get the value of the [property@Malcontent.User:display-name] property.
410 : : *
411 : : * Returns: (not nullable): the user’s display name
412 : : * Since: 0.14.0
413 : : */
414 : : const char *
415 : 0 : mct_user_get_display_name (MctUser *self)
416 : : {
417 : : const char *name;
418 : :
419 : 0 : g_return_val_if_fail (MCT_IS_USER (self), NULL);
420 : :
421 : 0 : name = mct_user_get_real_name (self);
422 [ # # ]: 0 : if (name != NULL)
423 : 0 : return name;
424 : :
425 : 0 : return mct_user_get_username (self);
426 : : }
427 : :
428 : : /**
429 : : * mct_user_get_user_type:
430 : : * @self: a user
431 : : *
432 : : * Get the value of the [property@Malcontent.User:user-type] property.
433 : : *
434 : : * Returns: the user’s account type
435 : : * Since: 0.14.0
436 : : */
437 : : MctUserType
438 : 0 : mct_user_get_user_type (MctUser *self)
439 : : {
440 : 0 : g_return_val_if_fail (MCT_IS_USER (self), MCT_USER_TYPE_UNKNOWN);
441 : :
442 [ # # ]: 0 : if (act_user_is_system_account (self->user))
443 : 0 : return MCT_USER_TYPE_SYSTEM;
444 : :
445 : : /* FIXME: Link this to the data in com.endlessm.ParentalControls.AccountInfo?
446 : : * Or drop that settings interface. */
447 [ # # # ]: 0 : switch (act_user_get_account_type (self->user))
448 : : {
449 : 0 : case ACT_USER_ACCOUNT_TYPE_STANDARD:
450 : 0 : return MCT_USER_TYPE_CHILD;
451 : 0 : case ACT_USER_ACCOUNT_TYPE_ADMINISTRATOR:
452 : 0 : return MCT_USER_TYPE_PARENT;
453 : 0 : default:
454 : 0 : return MCT_USER_TYPE_UNKNOWN;
455 : : }
456 : : }
457 : :
458 : : /**
459 : : * mct_user_get_icon_path:
460 : : * @self: a user
461 : : *
462 : : * Get the value of the [property@Malcontent.User:icon-path] property.
463 : : *
464 : : * Returns: (nullable): the user’s avatar icon path, or `NULL` if not set
465 : : * Since: 0.14.0
466 : : */
467 : : const char *
468 : 0 : mct_user_get_icon_path (MctUser *self)
469 : : {
470 : 0 : g_return_val_if_fail (MCT_IS_USER (self), NULL);
471 : :
472 : 0 : return act_user_get_icon_file (self->user);
473 : : }
474 : :
475 : : /**
476 : : * mct_user_get_login_time:
477 : : * @self: a user
478 : : *
479 : : * Get the value of the [property@Malcontent.User:login-time] property.
480 : : *
481 : : * Returns: the login time
482 : : * Since: 0.14.0
483 : : */
484 : : uint64_t
485 : 0 : mct_user_get_login_time (MctUser *self)
486 : : {
487 : 0 : g_return_val_if_fail (MCT_IS_USER (self), 0);
488 : :
489 : 0 : return act_user_get_login_time (self->user);
490 : : }
491 : :
492 : : /**
493 : : * mct_user_get_locale:
494 : : * @self: a user
495 : : *
496 : : * Get the value of the [property@Malcontent.User:locale] property.
497 : : *
498 : : * Returns: (nullable): the user’s locale, `""` if the system default, or `NULL`
499 : : * if not set
500 : : * Since: 0.14.0
501 : : */
502 : : const char *
503 : 0 : mct_user_get_locale (MctUser *self)
504 : : {
505 : 0 : g_return_val_if_fail (MCT_IS_USER (self), NULL);
506 : :
507 : 0 : return act_user_get_language (self->user);
508 : : }
509 : :
510 : : /**
511 : : * mct_user_get_act_user:
512 : : * @self: a user
513 : : *
514 : : * Get the AccountsService user object from @self.
515 : : *
516 : : * This is an internal getter method.
517 : : *
518 : : * Returns: (not nullable) (transfer none): an AccountsService user
519 : : * Since: 0.14.0
520 : : */
521 : : ActUser *
522 : 0 : mct_user_get_act_user (MctUser *self)
523 : : {
524 : 0 : g_return_val_if_fail (MCT_IS_USER (self), NULL);
525 : :
526 : 0 : return self->user;
527 : : }
528 : :
529 : : /**
530 : : * mct_user_equal:
531 : : * @a: a user
532 : : * @b: another user
533 : : *
534 : : * Check whether two users are equal.
535 : : *
536 : : * This compares their user IDs for equality, and is equivalent to:
537 : : * ```c
538 : : * mct_user_get_uid (a) == mct_user_get_uid (b)
539 : : * ```
540 : : *
541 : : * User IDs are a primary key for users in Unix.
542 : : *
543 : : * Returns: true if @a and @b are the same user, false otherwise
544 : : * Since: 0.14.0
545 : : */
546 : : gboolean
547 : 0 : mct_user_equal (MctUser *a,
548 : : MctUser *b)
549 : : {
550 : 0 : g_return_val_if_fail (MCT_IS_USER (a), FALSE);
551 : 0 : g_return_val_if_fail (MCT_IS_USER (b), FALSE);
552 : :
553 : 0 : return mct_user_get_uid (a) == mct_user_get_uid (b);
554 : : }
555 : :
556 : : /**
557 : : * mct_user_is_in_same_family:
558 : : * @self: a user
559 : : * @other: another user
560 : : *
561 : : * Calculate whether two users are in the same family.
562 : : *
563 : : * See the documentation for [class@Malcontent.UserManager] for the definition
564 : : * of a family.
565 : : *
566 : : * Returns: true if @self and @other are in the same family, false otherwise
567 : : * Since: 0.14.0
568 : : */
569 : : gboolean
570 : 0 : mct_user_is_in_same_family (MctUser *self,
571 : : MctUser *other)
572 : : {
573 : 0 : g_return_val_if_fail (MCT_IS_USER (self), FALSE);
574 : 0 : g_return_val_if_fail (MCT_IS_USER (other), FALSE);
575 : :
576 : : /* FIXME: For now, we support very minimal family setups. All standard and
577 : : * admin users on the system are in the same family. System users are not in
578 : : * a family.
579 : : *
580 : : * Eventually we want to support more complicated family groups like
581 : : * https://gitlab.gnome.org/Teams/Design/app-mockups/-/raw/fedd34d5c2eff8a95951807bf0a6de3e184d6065/family/family-overview.png
582 : : * (from https://gitlab.gnome.org/Teams/Design/app-mockups/-/issues/118). */
583 [ # # # # ]: 0 : return (!act_user_is_system_account (self->user) &&
584 : 0 : !act_user_is_system_account (other->user));
585 : : }
586 : :
587 : : /**
588 : : * mct_user_is_parent_of:
589 : : * @self: a user
590 : : * @other: another user
591 : : *
592 : : * Calculate whether one user is the parent of another user.
593 : : *
594 : : * See the documentation for [class@Malcontent.UserManager] for the definition
595 : : * of a family and family relationships.
596 : : *
597 : : * Returns: true if @self is a parent of @other; false otherwise
598 : : * Since: 0.14.0
599 : : */
600 : : gboolean
601 : 0 : mct_user_is_parent_of (MctUser *self,
602 : : MctUser *other)
603 : : {
604 [ # # ]: 0 : return (mct_user_is_in_same_family (self, other) &&
605 [ # # # # ]: 0 : mct_user_get_user_type (self) == MCT_USER_TYPE_PARENT &&
606 : 0 : mct_user_get_user_type (other) == MCT_USER_TYPE_CHILD);
607 : : }
|