gnome-control-center/panels/user-accounts/user-utils.c

465 lines
14 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright 2009-2010 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, see <http://www.gnu.org/licenses/>.
*
* Written by: Matthias Clasen <mclasen@redhat.com>
*/
#include "config.h"
#include <math.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <limits.h>
#include <unistd.h>
#include <utmpx.h>
#include <pwd.h>
#ifdef __FreeBSD__
#include <sysexits.h>
#endif
#include <gio/gio.h>
#include <gio/gunixoutputstream.h>
#include <glib/gi18n.h>
#include <glib/gstdio.h>
#include "user-utils.h"
#define IMAGE_SIZE 512
/* Taken from defines.h in shadow-utils. On Linux, this value is much smaller
* than the sysconf limit LOGIN_NAME_MAX, and values larger than this will
* result in failure when running useradd. We could check UT_NAMESIZE instead,
* but that is nonstandard. Better to use POSIX utmpx.
*/
gsize
get_username_max_length (void)
{
return sizeof (((struct utmpx *)NULL)->ut_user);
}
gboolean
is_username_used (const gchar *username)
{
struct passwd *pwent;
if (username == NULL || username[0] == '\0') {
return FALSE;
}
pwent = getpwnam (username);
return pwent != NULL;
}
gboolean
is_valid_name (const gchar *name)
{
gboolean is_empty = TRUE;
const gchar *c;
if (name == NULL)
return is_empty;
/* Valid names must contain:
* 1) at least one character.
* 2) at least one non-"space" character.
*/
for (c = name; *c; c++) {
gunichar unichar;
unichar = g_utf8_get_char_validated (c, -1);
/* Partial UTF-8 sequence or end of string */
if (unichar == (gunichar) -1 || unichar == (gunichar) -2)
break;
/* Check for non-space character */
if (!g_unichar_isspace (unichar)) {
is_empty = FALSE;
break;
}
}
return !is_empty;
}
typedef struct {
gchar *username;
gchar *tip;
} isValidUsernameData;
static void
is_valid_username_data_free (isValidUsernameData *data)
{
g_clear_pointer (&data->username, g_free);
g_clear_pointer (&data->tip, g_free);
g_free (data);
}
#ifdef __FreeBSD__
/* Taken from pw(8) man page. */
#define E_SUCCESS EX_OK
#define E_BAD_ARG EX_DATAERR
#define E_NOTFOUND EX_NOUSER
#else
/* Taken from usermod.c in shadow-utils. */
#define E_SUCCESS 0
#define E_BAD_ARG 3
#define E_NOTFOUND 6
#endif
static void
is_valid_username_child_watch_cb (GPid pid,
gint status,
gpointer user_data)
{
g_autoptr(GTask) task = G_TASK (user_data);
isValidUsernameData *data = g_task_get_task_data (task);
GError *error = NULL;
gboolean valid = FALSE;
const gchar *tip = NULL;
if (WIFEXITED (status)) {
switch (WEXITSTATUS (status)) {
case E_NOTFOUND:
valid = TRUE;
break;
case E_BAD_ARG:
tip = _("The username should usually only consist of lower case letters from a-z, digits and the following characters: - _");
valid = FALSE;
break;
case E_SUCCESS:
tip = _("Sorry, that user name isnt available. Please try another.");
valid = FALSE;
break;
}
}
if (valid || tip != NULL) {
data->tip = g_strdup (tip);
g_task_return_boolean (task, valid);
}
else {
g_spawn_check_exit_status (status, &error);
g_task_return_error (task, error);
}
g_spawn_close_pid (pid);
}
void
is_valid_username_async (const gchar *username,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer callback_data)
{
g_autoptr(GTask) task = NULL;
isValidUsernameData *data;
gchar *argv[6];
GPid pid;
GError *error = NULL;
task = g_task_new (NULL, cancellable, callback, callback_data);
g_task_set_source_tag (task, is_valid_username_async);
data = g_new0 (isValidUsernameData, 1);
data->username = g_strdup (username);
g_task_set_task_data (task, data, (GDestroyNotify) is_valid_username_data_free);
if (username == NULL || username[0] == '\0') {
g_task_return_boolean (task, FALSE);
return;
}
else if (strlen (username) > get_username_max_length ()) {
data->tip = g_strdup (_("The username is too long."));
g_task_return_boolean (task, FALSE);
return;
}
#ifdef __FreeBSD__
/* Abuse "pw usershow -n <name>" in the same way as the code below. We
* don't use "pw usermod -n <name> -N -l <newname>" here because it has
* a special case for "root" to reject changes to the root user.
*/
argv[0] = "pw";
argv[1] = "usershow";
argv[2] = "-n";
argv[3] = data->username;
argv[4] = NULL;
#else
/* "usermod --login" is meant to be used to change a username, but the
* exit codes can be safely abused to check the validity of username.
* However, the current "usermod" implementation may change in the
* future, so it would be nice to have some official way for this
* instead of relying on the current "--login" implementation.
*/
argv[0] = "/usr/sbin/usermod";
argv[1] = "--login";
argv[2] = data->username;
argv[3] = "--";
argv[4] = data->username;
argv[5] = NULL;
#endif
if (!g_spawn_async (NULL, argv, NULL,
G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD |
G_SPAWN_STDOUT_TO_DEV_NULL | G_SPAWN_STDERR_TO_DEV_NULL,
NULL, NULL, &pid, &error)) {
g_task_return_error (task, error);
return;
}
g_child_watch_add (pid, (GChildWatchFunc) is_valid_username_child_watch_cb, task);
g_steal_pointer (&task);
}
gboolean
is_valid_username_finish (GAsyncResult *result,
gchar **tip,
gchar **username,
GError **error)
{
GTask *task;
isValidUsernameData *data;
g_return_val_if_fail (g_task_is_valid (result, NULL), FALSE);
task = G_TASK (result);
data = g_task_get_task_data (task);
if (tip != NULL) {
*tip = g_steal_pointer (&data->tip);
}
if (username != NULL)
*username = g_steal_pointer (&data->username);
return g_task_propagate_boolean (task, error);
}
GdkPixbuf *
round_image (GdkPixbuf *pixbuf)
{
GdkPixbuf *dest = NULL;
cairo_surface_t *surface;
cairo_t *cr;
gint size;
size = gdk_pixbuf_get_width (pixbuf);
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, size, size);
cr = cairo_create (surface);
/* Clip a circle */
cairo_arc (cr, size/2, size/2, size/2, 0, 2 * G_PI);
cairo_clip (cr);
cairo_new_path (cr);
gdk_cairo_set_source_pixbuf (cr, pixbuf, 0, 0);
cairo_paint (cr);
dest = gdk_pixbuf_get_from_surface (surface, 0, 0, size, size);
cairo_surface_destroy (surface);
cairo_destroy (cr);
return dest;
}
static gchar *
extract_initials_from_name (const gchar *name)
{
GString *initials;
g_autofree gchar *p = NULL;
g_autofree gchar *normalized = NULL;
gunichar unichar;
gpointer q = NULL;
g_return_val_if_fail (name != NULL, NULL);
p = g_utf8_strup (name, -1);
normalized = g_utf8_normalize (g_strstrip (p), -1, G_NORMALIZE_DEFAULT_COMPOSE);
if (normalized == NULL) {
return NULL;
}
initials = g_string_new ("");
unichar = g_utf8_get_char (normalized);
g_string_append_unichar (initials, unichar);
q = g_utf8_strrchr (normalized, -1, ' ');
if (q != NULL && g_utf8_next_char (q) != NULL) {
q = g_utf8_next_char (q);
unichar = g_utf8_get_char (q);
g_string_append_unichar (initials, unichar);
}
return g_string_free (initials, FALSE);
}
static GdkRGBA
get_color_for_name (const gchar *name)
{
// https://gitlab.gnome.org/Community/Design/HIG-app-icons/blob/master/GNOME%20HIG.gpl
static gdouble gnome_color_palette[][3] = {
{ 98, 160, 234 },
{ 53, 132, 228 },
{ 28, 113, 216 },
{ 26, 95, 180 },
{ 87, 227, 137 },
{ 51, 209, 122 },
{ 46, 194, 126 },
{ 38, 162, 105 },
{ 248, 228, 92 },
{ 246, 211, 45 },
{ 245, 194, 17 },
{ 229, 165, 10 },
{ 255, 163, 72 },
{ 255, 120, 0 },
{ 230, 97, 0 },
{ 198, 70, 0 },
{ 237, 51, 59 },
{ 224, 27, 36 },
{ 192, 28, 40 },
{ 165, 29, 45 },
{ 192, 97, 203 },
{ 163, 71, 186 },
{ 129, 61, 156 },
{ 97, 53, 131 },
{ 181, 131, 90 },
{ 152, 106, 68 },
{ 134, 94, 60 },
{ 99, 69, 44 }
};
GdkRGBA color = { 255, 255, 255, 1.0 };
guint hash;
gint number_of_colors;
gint idx;
if (name == NULL || strlen (name) == 0)
return color;
hash = g_str_hash (name);
number_of_colors = G_N_ELEMENTS (gnome_color_palette);
idx = hash % number_of_colors;
color.red = gnome_color_palette[idx][0];
color.green = gnome_color_palette[idx][1];
color.blue = gnome_color_palette[idx][2];
return color;
}
static cairo_surface_t *
generate_user_picture (const gchar *name, gint size)
{
PangoFontDescription *font_desc;
g_autofree gchar *initials = extract_initials_from_name (name);
g_autofree gchar *font = g_strdup_printf ("Sans %d", (int)ceil (size / 2.5));
PangoLayout *layout;
GdkRGBA color = get_color_for_name (name);
cairo_surface_t *surface;
gint width, height;
cairo_t *cr;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
size,
size);
cr = cairo_create (surface);
cairo_rectangle (cr, 0, 0, size, size);
cairo_set_source_rgb (cr, color.red/255.0, color.green/255.0, color.blue/255.0);
cairo_fill (cr);
/* Draw the initials on top */
cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
layout = pango_cairo_create_layout (cr);
pango_layout_set_text (layout, initials, -1);
font_desc = pango_font_description_from_string (font);
pango_layout_set_font_description (layout, font_desc);
pango_font_description_free (font_desc);
pango_layout_get_size (layout, &width, &height);
cairo_translate (cr, size/2, size/2);
cairo_move_to (cr, - ((double)width / PANGO_SCALE)/2, - ((double)height/PANGO_SCALE)/2);
pango_cairo_show_layout (cr, layout);
cairo_destroy (cr);
return surface;
}
void
set_user_icon_data (ActUser *user,
GdkPixbuf *pixbuf)
{
g_autofree gchar *path = NULL;
gint fd;
g_autoptr(GOutputStream) stream = NULL;
g_autoptr(GError) error = NULL;
path = g_build_filename (g_get_tmp_dir (), "gnome-control-center-user-icon-XXXXXX", NULL);
fd = g_mkstemp (path);
if (fd == -1) {
g_warning ("failed to create temporary file for image data");
return;
}
stream = g_unix_output_stream_new (fd, TRUE);
if (!gdk_pixbuf_save_to_stream (pixbuf, stream, "png", NULL, &error, NULL)) {
g_warning ("failed to save image: %s", error->message);
return;
}
act_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);
}
GdkPixbuf *
generate_default_avatar (ActUser *user, gint size)
{
const gchar *name;
GdkPixbuf *pixbuf = NULL;
cairo_surface_t *surface;
name = act_user_get_real_name (user);
if (name == NULL)
name = "";
surface = generate_user_picture (name, size);
pixbuf = gdk_pixbuf_get_from_surface (surface, 0, 0, size, size);
cairo_surface_destroy (surface);
return pixbuf;
}
void
set_default_avatar (ActUser *user)
{
g_autoptr(GdkPixbuf) pixbuf = NULL;
pixbuf = generate_default_avatar (user, IMAGE_SIZE);
set_user_icon_data (user, pixbuf);
}