/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- * * Copyright (C) 2004-2005 James M. Cape . * Copyright (C) 2007-2008 William Jon McCann * Copyright (C) 2009 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #define _XOPEN_SOURCE #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "um-user.h" #include "um-account-type.h" #include "um-utils.h" #define UM_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_USER, UmUserClass)) #define UM_IS_USER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_USER)) #define UM_USER_GET_CLASS(object) (G_TYPE_INSTANCE_GET_CLASS ((object), UM_TYPE_USER, UmUserClass)) #define MAX_FILE_SIZE 65536 typedef struct { uid_t uid; gchar *user_name; gchar *real_name; gint account_type; gint password_mode; gchar *password_hint; gchar *email; gchar *language; gchar *location; guint64 login_frequency; gchar *icon_file; gboolean locked; gboolean automatic_login; gboolean system_account; } UserProperties; static void user_properties_free (UserProperties *props) { g_free (props->user_name); g_free (props->real_name); g_free (props->password_hint); g_free (props->email); g_free (props->language); g_free (props->location); g_free (props->icon_file); g_free (props); } static UserProperties * user_properties_get (GDBusConnection *bus, const gchar *object_path) { GVariant *result; GVariantIter *iter; gchar *key; GVariant *value; UserProperties *props; GError *error = NULL; result = g_dbus_connection_call_sync (bus, "org.freedesktop.Accounts", object_path, "org.freedesktop.DBus.Properties", "GetAll", g_variant_new ("(s)", "org.freedesktop.Accounts.User"), G_VARIANT_TYPE ("(a{sv})"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_debug ("Error calling GetAll() when retrieving properties for %s: %s", object_path, error->message); g_error_free (error); return NULL; } props = g_new0 (UserProperties, 1); g_variant_get (result, "(a{sv})", &iter); while (g_variant_iter_loop (iter, "{&sv}", &key, &value)) { if (strcmp (key, "Uid") == 0) { g_variant_get (value, "t", &props->uid); } else if (strcmp (key, "UserName") == 0) { g_variant_get (value, "s", &props->user_name); } else if (strcmp (key, "RealName") == 0) { g_variant_get (value, "s", &props->real_name); } else if (strcmp (key, "AccountType") == 0) { g_variant_get (value, "i", &props->account_type); } else if (strcmp (key, "Email") == 0) { g_variant_get (value, "s", &props->email); } else if (strcmp (key, "Language") == 0) { g_variant_get (value, "s", &props->language); } else if (strcmp (key, "Location") == 0) { g_variant_get (value, "s", &props->location); } else if (strcmp (key, "LoginFrequency") == 0) { g_variant_get (value, "t", &props->login_frequency); } else if (strcmp (key, "IconFile") == 0) { g_variant_get (value, "s", &props->icon_file); } else if (strcmp (key, "Locked") == 0) { g_variant_get (value, "b", &props->locked); } else if (strcmp (key, "AutomaticLogin") == 0) { g_variant_get (value, "b", &props->automatic_login); } else if (strcmp (key, "SystemAccount") == 0) { g_variant_get (value, "b", &props->system_account); } else if (strcmp (key, "PasswordMode") == 0) { g_variant_get (value, "i", &props->password_mode); } else if (strcmp (key, "PasswordHint") == 0) { g_variant_get (value, "s", &props->password_hint); } else if (strcmp (key, "HomeDirectory") == 0) { /* ignore */ } else if (strcmp (key, "Shell") == 0) { /* ignore */ } else { g_debug ("unhandled property %s", key); } } g_variant_iter_free (iter); g_variant_unref (result); return props; } struct _UmUser { GObject parent; GDBusConnection *bus; GDBusProxy *proxy; gchar *object_path; UserProperties *props; gchar *display_name; }; typedef struct _UmUserClass { GObjectClass parent_class; } UmUserClass; enum { CHANGED, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; static void um_user_finalize (GObject *object); G_DEFINE_TYPE (UmUser, um_user, G_TYPE_OBJECT) static void um_user_class_init (UmUserClass *class) { GObjectClass *gobject_class; gobject_class = G_OBJECT_CLASS (class); gobject_class->finalize = um_user_finalize; signals[CHANGED] = g_signal_new ("changed", G_TYPE_FROM_CLASS (class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); } static void um_user_init (UmUser *user) { } static void um_user_finalize (GObject *object) { UmUser *user; user = UM_USER (object); g_free (user->display_name); g_object_unref (user->bus); g_free (user->object_path); if (user->proxy != NULL) g_object_unref (user->proxy); if (user->props != NULL) user_properties_free (user->props); (*G_OBJECT_CLASS (um_user_parent_class)->finalize) (object); } uid_t um_user_get_uid (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), -1); return user->props->uid; } const gchar * um_user_get_real_name (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->real_name; } const gchar * um_user_get_display_name (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); if (user->display_name) return user->display_name; if (user->props->real_name && *user->props->real_name != '\0') return user->props->real_name; return user->props->user_name; } const gchar * um_user_get_user_name (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->user_name; } gint um_user_get_account_type (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), UM_ACCOUNT_TYPE_STANDARD); return user->props->account_type; } gulong um_user_get_login_frequency (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), 0); return user->props->login_frequency; } gint um_user_collate (UmUser *user1, UmUser *user2) { const char *str1; const char *str2; gulong num1; gulong num2; g_return_val_if_fail (UM_IS_USER (user1), 0); g_return_val_if_fail (UM_IS_USER (user2), 0); num1 = user1->props->login_frequency; num2 = user2->props->login_frequency; if (num1 > num2) { return -1; } if (num1 < num2) { return 1; } /* if login frequency is equal try names */ if (user1->props->real_name != NULL) { str1 = user1->props->real_name; } else { str1 = user1->props->user_name; } if (user2->props->real_name != NULL) { str2 = user2->props->real_name; } else { str2 = user2->props->user_name; } if (str1 == NULL && str2 != NULL) { return -1; } if (str1 != NULL && str2 == NULL) { return 1; } if (str1 == NULL && str2 == NULL) { return 0; } return g_utf8_collate (str1, str2); } static gboolean check_user_file (const char *filename, gssize max_file_size) { struct stat fileinfo; if (max_file_size < 0) { max_file_size = G_MAXSIZE; } /* Exists/Readable? */ if (stat (filename, &fileinfo) < 0) { g_debug ("File does not exist"); return FALSE; } /* Is a regular file */ if (G_UNLIKELY (!S_ISREG (fileinfo.st_mode))) { g_debug ("File is not a regular file"); return FALSE; } /* Size is kosher? */ if (G_UNLIKELY (fileinfo.st_size > max_file_size)) { g_debug ("File is too large"); return FALSE; } return TRUE; } static cairo_surface_t * surface_from_pixbuf (GdkPixbuf *pixbuf) { cairo_surface_t *surface; cairo_t *cr; surface = cairo_image_surface_create (gdk_pixbuf_get_has_alpha (pixbuf) ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, gdk_pixbuf_get_width (pixbuf), gdk_pixbuf_get_height (pixbuf)); cr = cairo_create (surface); gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0); cairo_paint (cr); cairo_destroy (cr); return surface; } /** * go_cairo_convert_data_to_pixbuf: * @src: a pointer to pixel data in cairo format * @dst: a pointer to pixel data in pixbuf format * @width: image width * @height: image height * @rowstride: data rowstride * * Converts the pixel data stored in @src in CAIRO_FORMAT_ARGB32 cairo format * to GDK_COLORSPACE_RGB pixbuf format and move them * to @dst. If @src == @dst, pixel are converted in place. **/ static void go_cairo_convert_data_to_pixbuf (unsigned char *dst, unsigned char const *src, int width, int height, int rowstride) { int i,j; unsigned int t; unsigned char a, b, c; g_return_if_fail (dst != NULL); #define MULT(d,c,a,t) G_STMT_START { t = (a)? c * 255 / a: 0; d = t;} G_STMT_END if (src == dst || src == NULL) { for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { #if G_BYTE_ORDER == G_LITTLE_ENDIAN MULT(a, dst[2], dst[3], t); MULT(b, dst[1], dst[3], t); MULT(c, dst[0], dst[3], t); dst[0] = a; dst[1] = b; dst[2] = c; #else MULT(a, dst[1], dst[0], t); MULT(b, dst[2], dst[0], t); MULT(c, dst[3], dst[0], t); dst[3] = dst[0]; dst[0] = a; dst[1] = b; dst[2] = c; #endif dst += 4; } dst += rowstride - width * 4; } } else { for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { #if G_BYTE_ORDER == G_LITTLE_ENDIAN MULT(dst[0], src[2], src[3], t); MULT(dst[1], src[1], src[3], t); MULT(dst[2], src[0], src[3], t); dst[3] = src[3]; #else MULT(dst[0], src[1], src[0], t); MULT(dst[1], src[2], src[0], t); MULT(dst[2], src[3], src[0], t); dst[3] = src[0]; #endif src += 4; dst += 4; } src += rowstride - width * 4; dst += rowstride - width * 4; } } #undef MULT } static void cairo_to_pixbuf (guint8 *src_data, GdkPixbuf *dst_pixbuf) { unsigned char *src; unsigned char *dst; guint w; guint h; guint rowstride; w = gdk_pixbuf_get_width (dst_pixbuf); h = gdk_pixbuf_get_height (dst_pixbuf); rowstride = gdk_pixbuf_get_rowstride (dst_pixbuf); dst = gdk_pixbuf_get_pixels (dst_pixbuf); src = src_data; go_cairo_convert_data_to_pixbuf (dst, src, w, h, rowstride); } static GdkPixbuf * frame_pixbuf (GdkPixbuf *source) { GdkPixbuf *dest; cairo_t *cr; cairo_surface_t *surface; guint w; guint h; guint rowstride; int frame_width; double radius; guint8 *data; frame_width = 2; w = gdk_pixbuf_get_width (source) + frame_width * 2; h = gdk_pixbuf_get_height (source) + frame_width * 2; radius = w / 10; dest = gdk_pixbuf_new (GDK_COLORSPACE_RGB, TRUE, 8, w, h); rowstride = gdk_pixbuf_get_rowstride (dest); data = g_new0 (guint8, h * rowstride); surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, w, h, rowstride); cr = cairo_create (surface); cairo_surface_destroy (surface); /* set up image */ cairo_rectangle (cr, 0, 0, w, h); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, 0.0); cairo_fill (cr); rounded_rectangle (cr, 1.0, 0.5, 0.5, radius, w - 1, h - 1); cairo_set_source_rgba (cr, 0.5, 0.5, 0.5, 0.3); cairo_fill_preserve (cr); surface = surface_from_pixbuf (source); cairo_set_source_surface (cr, surface, frame_width, frame_width); cairo_fill (cr); cairo_surface_destroy (surface); cairo_to_pixbuf (data, dest); cairo_destroy (cr); g_free (data); return dest; } GdkPixbuf * um_user_render_icon (UmUser *user, gboolean with_frame, gint icon_size) { GdkPixbuf *pixbuf; GdkPixbuf *framed; gboolean res; GError *error; g_return_val_if_fail (UM_IS_USER (user), NULL); g_return_val_if_fail (icon_size > 12, NULL); pixbuf = NULL; if (user->props->icon_file) { res = check_user_file (user->props->icon_file, MAX_FILE_SIZE); if (res) { pixbuf = gdk_pixbuf_new_from_file_at_size (user->props->icon_file, icon_size, icon_size, NULL); } else { pixbuf = NULL; } } if (pixbuf != NULL) { goto out; } error = NULL; pixbuf = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), "avatar-default", icon_size, GTK_ICON_LOOKUP_FORCE_SIZE, &error); if (error) { g_warning ("%s", error->message); g_error_free (error); } out: if (pixbuf != NULL && with_frame) { framed = frame_pixbuf (pixbuf); if (framed != NULL) { g_object_unref (pixbuf); pixbuf = framed; } } return pixbuf; } const gchar * um_user_get_email (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->email; } const gchar * um_user_get_language (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); if (*user->props->language == '\0') return NULL; return user->props->language; } const gchar * um_user_get_location (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->location; } gint um_user_get_password_mode (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), UM_PASSWORD_MODE_NONE); return user->props->password_mode; } const char * um_user_get_password_hint (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->password_hint; } const char * um_user_get_icon_file (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->props->icon_file; } gboolean um_user_get_locked (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), FALSE); return user->props->locked; } gboolean um_user_get_automatic_login (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), FALSE); return user->props->automatic_login; } gboolean um_user_is_system_account (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), FALSE); return user->props->system_account; } const gchar * um_user_get_object_path (UmUser *user) { g_return_val_if_fail (UM_IS_USER (user), NULL); return user->object_path; } static gboolean update_info (UmUser *user) { UserProperties *props; props = user_properties_get (user->bus, user->object_path); if (props != NULL) { if (user->props != NULL) user_properties_free (user->props); user->props = props; return TRUE; } else { return FALSE; } } static void user_signal_cb (GDBusProxy *proxy, gchar *sender_name, gchar *signal_name, GVariant *parameters, UmUser *user) { if (strcmp (signal_name, "Changed") == 0) { if (update_info (user)) { if (user->display_name != NULL) { um_user_show_full_display_name (user); } g_signal_emit (user, signals[CHANGED], 0); } } } UmUser * um_user_new_from_object_path (const gchar *object_path) { UmUser *user; GError *error = NULL; user = (UmUser *)g_object_new (UM_TYPE_USER, NULL); user->object_path = g_strdup (object_path); user->bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error); if (user->bus == NULL) { g_warning ("Couldn't connect to system bus: %s", error->message); g_error_free (error); goto error; } user->proxy = g_dbus_proxy_new_sync (user->bus, G_DBUS_PROXY_FLAGS_NONE, NULL, "org.freedesktop.Accounts", user->object_path, "org.freedesktop.Accounts.User", NULL, &error); if (user->proxy == NULL) { g_warning ("Couldn't get user proxy: %s", error->message); g_error_free (error); goto error; } g_dbus_proxy_set_default_timeout (user->proxy, INT_MAX); g_signal_connect (user->proxy, "g-signal", G_CALLBACK (user_signal_cb), user); if (!update_info (user)) goto error; return user; error: g_object_unref (user); return NULL; } void um_user_set_email (UmUser *user, const gchar *email) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_sync (user->proxy, "SetEmail", g_variant_new ("(s)", email), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetEmail call failed: %s", error->message); g_error_free (error); return; } g_variant_unref (result); } void um_user_set_language (UmUser *user, const gchar *language) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_sync (user->proxy, "SetLanguage", g_variant_new ("(s)", language), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetLanguage call failed: %s", error->message); g_error_free (error); return; } g_variant_unref (result); } void um_user_set_location (UmUser *user, const gchar *location) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_sync (user->proxy, "SetLocation", g_variant_new ("(s)", location), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetLocation call failed: %s", error->message); g_error_free (error); return; } g_variant_unref (result); } void um_user_set_user_name (UmUser *user, const gchar *user_name) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_sync (user->proxy, "SetUserName", g_variant_new ("(s)", user_name), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetUserName call failed: %s", error->message); g_error_free (error); return; } g_variant_unref (result); } void um_user_set_real_name (UmUser *user, const gchar *real_name) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_sync (user->proxy, "SetRealName", g_variant_new ("(s)", real_name), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetRealName call failed: %s", error->message); g_error_free (error); return; } g_variant_unref (result); } void um_user_set_icon_file (UmUser *user, const gchar *icon_file) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_sync (user->proxy, "SetIconFile", g_variant_new ("(s)", icon_file), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetIconFile call failed: %s", error->message); g_error_free (error); return; } g_variant_unref (result); } void um_user_set_icon_data (UmUser *user, GdkPixbuf *pixbuf) { gchar *path; gint fd; GOutputStream *stream; GError *error; path = g_build_filename (g_get_tmp_dir (), "usericonXXXXXX", NULL); fd = g_mkstemp (path); if (fd == -1) { g_warning ("failed to create temporary file for image data"); g_free (path); return; } stream = g_unix_output_stream_new (fd, TRUE); error = NULL; if (!gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, &error, NULL)) { g_warning ("failed to save image: %s", error->message); g_error_free (error); g_object_unref (stream); return; } g_object_unref (stream); um_user_set_icon_file (user, path); /* if we ever make the dbus call async, the g_remove call needs * to wait for its completion */ g_remove (path); g_free (path); } void um_user_set_account_type (UmUser *user, gint account_type) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_sync (user->proxy, "SetAccountType", g_variant_new ("(i)", account_type), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetAccountType call failed: %s", error->message); g_error_free (error); return; } g_variant_unref (result); } static gchar salt_char (GRand *rand) { gchar salt[] = "ABCDEFGHIJKLMNOPQRSTUVXYZ" "abcdefghijklmnopqrstuvxyz" "./0123456789"; return salt[g_rand_int_range (rand, 0, G_N_ELEMENTS (salt))]; } static gchar * make_crypted (const gchar *plain) { GString *salt; gchar *result; GRand *rand; gint i; rand = g_rand_new (); salt = g_string_sized_new (21); /* SHA 256 */ g_string_append (salt, "$6$"); for (i = 0; i < 16; i++) { g_string_append_c (salt, salt_char (rand)); } g_string_append_c (salt, '$'); result = g_strdup (crypt (plain, salt->str)); g_string_free (salt, TRUE); g_rand_free (rand); return result; } void um_user_set_password (UmUser *user, gint password_mode, const gchar *password, const gchar *hint) { GError *error = NULL; gchar *crypted; if (password_mode == 0) { GVariant *result; crypted = make_crypted (password); result = g_dbus_proxy_call_sync (user->proxy, "SetPassword", g_variant_new ("(ss)", crypted, hint), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetPassword call failed: %s", error->message); g_error_free (error); } else g_variant_unref (result); memset (crypted, 0, strlen (crypted)); g_free (crypted); } else if (password_mode == 3 || password_mode == 4) { GVariant *result; /* FIXME: this is a slightly odd side-effect: * you disable the account, and autologin flips * we should remove that once gdm knows to * ignore autologin for disabled accounts */ if (password_mode == 3 && um_user_get_automatic_login (user)) { um_user_set_automatic_login (user, FALSE); } result = g_dbus_proxy_call_sync (user->proxy, "SetLocked", g_variant_new ("(b)", password_mode == 3), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetLocked call failed: %s", error->message); g_error_free (error); } else g_variant_unref (result); } else { GVariant *result; result = g_dbus_proxy_call_sync (user->proxy, "SetPasswordMode", g_variant_new ("(i)", password_mode), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetPasswordMode call failed: %s", error->message); g_error_free (error); } else g_variant_unref (result); } } #ifdef HAVE_SYSTEMD #include gboolean um_user_is_logged_in (UmUser *user) { int n_sessions; n_sessions = sd_uid_get_sessions (um_user_get_uid (user), 0, NULL) > 0; return n_sessions > 0; } #else gboolean um_user_is_logged_in (UmUser *user) { GVariant *result; GVariantIter *iter; gint n_sessions; GError *error = NULL; result = g_dbus_connection_call_sync (user->bus, "org.freedesktop.ConsoleKit", "/org/freedesktop/ConsoleKit/Manager", "org.freedesktop.ConsoleKit.Manager", "GetSessionsForUnixUser", g_variant_new ("(u)", um_user_get_uid (user)), G_VARIANT_TYPE ("(ao)"), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("GetSessionsForUnixUser failed: %s", error->message); g_error_free (error); return FALSE; } g_variant_get (result, "(ao)", &iter); n_sessions = g_variant_iter_n_children (iter); g_variant_iter_free (iter); g_variant_unref (result); return n_sessions > 0; } #endif void um_user_set_automatic_login (UmUser *user, gboolean enabled) { GVariant *result; GError *error = NULL; result = g_dbus_proxy_call_sync (user->proxy, "SetAutomaticLogin", g_variant_new ("(b)", enabled), G_DBUS_CALL_FLAGS_NONE, -1, NULL, &error); if (!result) { g_warning ("SetAutomaticLogin call failed: %s", error->message); g_error_free (error); return; } g_variant_unref (result); } void um_user_show_full_display_name (UmUser *user) { char *uniq_name; g_return_if_fail (UM_IS_USER (user)); if (user->props->real_name != NULL) { uniq_name = g_strdup_printf ("%s (%s)", user->props->real_name, user->props->user_name); } else { uniq_name = NULL; } if (uniq_name && g_strcmp0 (uniq_name, user->display_name) != 0) { g_free (user->display_name); user->display_name = uniq_name; } else { g_free (uniq_name); } } void um_user_show_short_display_name (UmUser *user) { g_return_if_fail (UM_IS_USER (user)); if (user->display_name) { g_free (user->display_name); user->display_name = NULL; } }