diff --git a/configure.ac b/configure.ac
index 72f60a19f..867f15c06 100644
--- a/configure.ac
+++ b/configure.ac
@@ -94,7 +94,7 @@ PKG_CHECK_MODULES(DBUS, dbus-1 dbus-glib-1)
PKG_CHECK_MODULES(GNOME_DESKTOP, gnome-desktop-3.0)
PKG_CHECK_MODULES(DEFAULT_APPLICATIONS_CAPPLET, libxml-2.0)
PKG_CHECK_MODULES(GSD_DBUS, gnome-settings-daemon)
-PKG_CHECK_MODULES(GIO, gio-2.0)
+PKG_CHECK_MODULES(GIO, gio-2.0 gio-unix-2.0)
PKG_CHECK_MODULES(XML, libxml-2.0)
PKG_CHECK_MODULES(CANBERRA, libcanberra-gtk3 >= $CANBERRA_REQUIRED_VERSION)
AC_SUBST(CANBERRA_CFLAGS)
@@ -112,6 +112,16 @@ AC_SUBST(GTK_ENGINE_DIR)
PKG_CHECK_MODULES(GLIB, glib-2.0)
+PKG_CHECK_MODULES(POLKIT, polkit-gobject-1)
+PKG_CHECK_MODULES(CHEESE, gstreamer-0.10 cheese-gtk >= 2.29.90, have_cheese=yes, have_cheese=no)
+
+if test x$have_cheese = xyes ; then
+ AC_DEFINE(HAVE_CHEESE, 1, [Define to 1 to enable cheese webcam support])
+fi
+
+AC_DEFINE_UNQUOTED([ISO_CODES_PREFIX],["`$PKG_CONFIG --variable=prefix iso-codes`"],[ISO codes prefix])
+ISO_CODES=iso-codes
+
dnl
dnl Check for Xft version 2; we build in extra functionality to the font capplet
dnl when we have it.
@@ -361,6 +371,10 @@ panels/sound/data/icons/scalable/devices/Makefile
panels/sound/data/sounds/Makefile
panels/universal-access/Makefile
panels/universal-access/gnome-universal-access-panel.desktop.in
+panels/user-accounts/Makefile
+panels/user-accounts/data/Makefile
+panels/user-accounts/data/gnome-user-accounts-panel.desktop.in
+panels/user-accounts/data/icons/Makefile
po/Makefile.in
shell/Makefile
shell/gnome-control-center.desktop.in
diff --git a/panels/Makefile.am b/panels/Makefile.am
index 225fcbc2e..945270e69 100644
--- a/panels/Makefile.am
+++ b/panels/Makefile.am
@@ -8,6 +8,7 @@ SUBDIRS= \
default-applications \
keybindings \
universal-access \
+ user-accounts \
datetime
-include $(top_srcdir)/git.mk
diff --git a/panels/user-accounts/Makefile.am b/panels/user-accounts/Makefile.am
new file mode 100644
index 000000000..8cebc10bb
--- /dev/null
+++ b/panels/user-accounts/Makefile.am
@@ -0,0 +1,102 @@
+SUBDIRS = data
+
+# This is used in GNOMECC_CAPPLETS_CFLAGS
+cappletname = user-accounts
+NULL =
+
+ccpanelsdir = $(PANELS_DIR)
+ccpanels_LTLIBRARIES = libuser-accounts.la
+
+AM_CPPFLAGS = \
+ $(GNOMECC_CAPPLETS_CFLAGS) \
+ -DDATADIR=\""$(datadir)"\" \
+ -DUIDIR=\""$(pkgdatadir)/ui/user-accounts"\" \
+ -DLIBLOCALEDIR=\""$(prefix)/lib/locale"\" \
+ -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
+ -DUM_PIXMAP_DIR=\""$(pkgdatadir)/pixmaps"\" \
+ $(PANEL_CFLAGS) \
+ $(GNOME_DESKTOP_CFLAGS) \
+ $(POLKIT_CFLAGS) \
+ $(CHEESE_CFLAGS) \
+ $(DBUS_CFLAGS) \
+ $(GIO_CFLAGS) \
+ $(WARN_CFLAGS)
+
+MARSHALFILES = marshal.c marshal.h
+BUILT_SOURCES = $(MARSHALFILES)
+
+marshal.h: fprintd-marshal.list
+ @GLIB_GENMARSHAL@ --prefix=fprintd_marshal $< --header > $@
+marshal.c: fprintd-marshal.list
+ @GLIB_GENMARSHAL@ --prefix=fprintd_marshal $< --body --header > $@
+
+libuser_accounts_la_CFLAGS = \
+ $(PANEL_CFLAGS)
+
+libuser_accounts_la_SOURCES = \
+ gdm-languages.h \
+ gdm-languages.c \
+ um-account-type.h \
+ um-account-type.c \
+ um-user.h \
+ um-user.c \
+ um-user-manager.h \
+ um-user-manager.c \
+ um-account-dialog.h \
+ um-account-dialog.c \
+ um-language-dialog.h \
+ um-language-dialog.c \
+ um-lockbutton.h \
+ um-lockbutton.c \
+ um-login-options.h \
+ um-login-options.c \
+ um-password-dialog.h \
+ um-password-dialog.c \
+ um-photo-dialog.h \
+ um-photo-dialog.c \
+ um-crop-area.h \
+ um-crop-area.c \
+ um-fingerprint-dialog.h \
+ um-fingerprint-dialog.c \
+ um-utils.h \
+ um-utils.c \
+ fingerprint-strings.h \
+ um-strength-bar.h \
+ um-strength-bar.c \
+ run-passwd.h \
+ run-passwd.c \
+ $(MARSHALFILES) \
+ um-editable-button.h \
+ um-editable-button.c \
+ um-editable-entry.h \
+ um-editable-entry.c \
+ um-editable-combo.h \
+ um-editable-combo.c \
+ um-user-panel.h \
+ um-user-panel.c \
+ um-user-module.c
+
+libuser_accounts_la_LIBADD = \
+ $(PANEL_LIBS) \
+ $(GNOME_DESKTOP_LIBS) \
+ $(POLKIT_LIBS) \
+ $(CHEESE_LIBS) \
+ $(DBUS_LIBS) \
+ $(GIO_LIBS) \
+ -lcrypt
+
+libuser_accounts_la_LDFLAGS = $(PANEL_LDFLAGS)
+
+EXTRA_DIST = \
+ locarchive.h \
+ fprintd-marshal.list
+
+CLEANFILES = \
+ $(BUILT_SOURCES) \
+ $(NULL)
+
+MAINTAINERCLEANFILES = \
+ *~ \
+ Makefile.in
+
+-include $(top_srcdir)/git.mk
diff --git a/panels/user-accounts/data/Makefile.am b/panels/user-accounts/data/Makefile.am
new file mode 100644
index 000000000..fbc382573
--- /dev/null
+++ b/panels/user-accounts/data/Makefile.am
@@ -0,0 +1,29 @@
+SUBDIRS = icons
+
+uidir = $(pkgdatadir)/ui/user-accounts
+ui_DATA = \
+ account-dialog.ui \
+ language-chooser.ui \
+ password-dialog.ui \
+ photo-dialog.ui \
+ user-accounts-dialog.ui \
+ account-fingerprint.ui
+
+@INTLTOOL_DESKTOP_RULE@
+
+desktopdir = $(datadir)/applications
+Desktop_in_files = gnome-user-accounts-panel.desktop.in
+desktop_DATA = $(Desktop_in_files:.desktop.in=.desktop)
+
+EXTRA_DIST = \
+ gnome-user-accounts-panel.desktop.in.in \
+ $(ui_DATA)
+
+CLEANFILES = \
+ gnome-user-accounts-panel.desktop \
+ $(NULL)
+
+DISTCLEANFILES = \
+ $(NULL)
+
+-include $(top_srcdir)/git.mk
diff --git a/panels/user-accounts/data/account-dialog.ui b/panels/user-accounts/data/account-dialog.ui
new file mode 100644
index 000000000..5e229cef0
--- /dev/null
+++ b/panels/user-accounts/data/account-dialog.ui
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+
+
diff --git a/panels/user-accounts/data/account-fingerprint.ui b/panels/user-accounts/data/account-fingerprint.ui
new file mode 100644
index 000000000..93a194e61
--- /dev/null
+++ b/panels/user-accounts/data/account-fingerprint.ui
@@ -0,0 +1,278 @@
+
+
+
+
+
+
+
+
+
+
+
+ Left thumb
+
+
+ Left middle finger
+
+
+ Left ring finger
+
+
+ Left little finger
+
+
+ Right thumb
+
+
+ Right middle finger
+
+
+ Right ring finger
+
+
+ Right little finger
+
+
+
+
+ 12
+ Enable Fingerprint Login
+ system-users
+
+
+
+
+
+ True
+ 12
+ vertical
+ 12
+
+
+ True
+ 6
+
+
+ True
+ gtk-dialog-info
+ 6
+
+
+ False
+ 0
+
+
+
+
+ True
+ To enable fingerprint login, you need to save one of your fingerprints, using the Acme Foobar 5000.
+ True
+
+
+ False
+ False
+ 1
+
+
+
+
+ 0
+
+
+
+
+ True
+ vertical
+
+
+ Right index finger
+ True
+ True
+ False
+ True
+ True
+ True
+
+
+ False
+ False
+ 0
+
+
+
+
+ Left index finger
+ True
+ True
+ False
+ True
+ True
+ radiobutton1
+
+
+ False
+ False
+ 1
+
+
+
+
+ True
+ 6
+
+
+ True
+ Other finger:
+ True
+ False
+ True
+ True
+ radiobutton1
+
+
+ False
+ False
+
+
+
+
+ True
+ False
+ model1
+
+
+
+ 0
+
+
+
+
+ False
+ False
+
+
+
+
+ False
+ 2
+
+
+
+
+ 1
+
+
+
+
+
+
+ True
+ vertical
+
+
+ True
+ In order to save your fingerprints, you need to swipe your thumb on the "Acme foobar" device.
+ True
+
+
+ False
+ False
+ 0
+
+
+
+
+ True
+
+
+
+
+
+ True
+ gtk-no
+ 6
+
+
+ 1
+
+
+
+
+ True
+ gtk-no
+ 6
+
+
+ 2
+
+
+
+
+ True
+ gtk-no
+ 6
+
+
+ 3
+
+
+
+
+ True
+ gtk-no
+ 6
+
+
+ 4
+
+
+
+
+ True
+ gtk-no
+ 6
+
+
+ 5
+
+
+
+
+
+
+
+ 1
+
+
+
+
+ True
+
+
+ False
+ False
+ 2
+
+
+
+
+
+
+ True
+ Your fingerprint was successfully saved. You should now be able to log in using your fingerprint reader.
+ True
+
+
+ summary
+
+
+
+
+ both
+
+
+
+
+
+
+
diff --git a/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in b/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in
new file mode 100644
index 000000000..af3102f24
--- /dev/null
+++ b/panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in
@@ -0,0 +1,15 @@
+[Desktop Entry]
+_Name=User Accounts
+_Comment=Add or remove users
+Exec=gnome-control-center user-accounts
+Icon=system-users
+Terminal=false
+Type=Application
+StartupNotify=true
+Categories=System;Settings;X-GNOME-Settings-Panel;
+OnlyShowIn=GNOME;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-control-center
+X-GNOME-Bugzilla-Component=user-accounts
+X-GNOME-Bugzilla-Version=@VERSION@
+X-GNOME-Settings-Panel=user-accounts
diff --git a/panels/user-accounts/data/icons/Makefile.am b/panels/user-accounts/data/icons/Makefile.am
new file mode 100644
index 000000000..218ac84b0
--- /dev/null
+++ b/panels/user-accounts/data/icons/Makefile.am
@@ -0,0 +1,31 @@
+pixmapsdir = $(pkgdatadir)/pixmaps
+pixmaps_DATA = \
+ gnome-about-me-lock.png \
+ gnome-about-me-lock-open.png \
+ left-index-finger.svg \
+ left-little-finger.svg \
+ left-middle-finger.svg \
+ left-ring-finger.svg \
+ left-thumb.svg \
+ print_error.svg \
+ print_ok.svg \
+ right-index-finger.svg \
+ right-little-finger.svg \
+ right-middle-finger.svg \
+ right-ring-finger.svg \
+ right-thumb.svg \
+ left-index-finger.png \
+ left-middle-finger.png \
+ left-little-finger.png \
+ left-ring-finger.png \
+ left-thumb.png \
+ print_error.png \
+ print_ok.png \
+ right-index-finger.png \
+ right-middle-finger.png \
+ right-little-finger.png \
+ right-ring-finger.png \
+ right-thumb.png
+
+EXTRA_DIST = $(pixmaps_DATA)
+
diff --git a/panels/user-accounts/data/icons/gnome-about-me-lock-open.png b/panels/user-accounts/data/icons/gnome-about-me-lock-open.png
new file mode 100644
index 000000000..1f0190541
Binary files /dev/null and b/panels/user-accounts/data/icons/gnome-about-me-lock-open.png differ
diff --git a/panels/user-accounts/data/icons/gnome-about-me-lock.png b/panels/user-accounts/data/icons/gnome-about-me-lock.png
new file mode 100644
index 000000000..f24ae3a60
Binary files /dev/null and b/panels/user-accounts/data/icons/gnome-about-me-lock.png differ
diff --git a/panels/user-accounts/data/icons/left-index-finger.png b/panels/user-accounts/data/icons/left-index-finger.png
new file mode 100644
index 000000000..1a9cb2c73
Binary files /dev/null and b/panels/user-accounts/data/icons/left-index-finger.png differ
diff --git a/panels/user-accounts/data/icons/left-index-finger.svg b/panels/user-accounts/data/icons/left-index-finger.svg
new file mode 100644
index 000000000..3c36aeaa7
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-index-finger.svg
@@ -0,0 +1,177 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/left-little-finger.png b/panels/user-accounts/data/icons/left-little-finger.png
new file mode 100644
index 000000000..978942eec
Binary files /dev/null and b/panels/user-accounts/data/icons/left-little-finger.png differ
diff --git a/panels/user-accounts/data/icons/left-little-finger.svg b/panels/user-accounts/data/icons/left-little-finger.svg
new file mode 100644
index 000000000..0835854f4
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-little-finger.svg
@@ -0,0 +1,180 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/left-middle-finger.png b/panels/user-accounts/data/icons/left-middle-finger.png
new file mode 100644
index 000000000..406925e97
Binary files /dev/null and b/panels/user-accounts/data/icons/left-middle-finger.png differ
diff --git a/panels/user-accounts/data/icons/left-middle-finger.svg b/panels/user-accounts/data/icons/left-middle-finger.svg
new file mode 100644
index 000000000..1082da2e0
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-middle-finger.svg
@@ -0,0 +1,180 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/left-ring-finger.png b/panels/user-accounts/data/icons/left-ring-finger.png
new file mode 100644
index 000000000..169ff6874
Binary files /dev/null and b/panels/user-accounts/data/icons/left-ring-finger.png differ
diff --git a/panels/user-accounts/data/icons/left-ring-finger.svg b/panels/user-accounts/data/icons/left-ring-finger.svg
new file mode 100644
index 000000000..50ace8076
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-ring-finger.svg
@@ -0,0 +1,180 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/left-thumb.png b/panels/user-accounts/data/icons/left-thumb.png
new file mode 100644
index 000000000..eaf875d00
Binary files /dev/null and b/panels/user-accounts/data/icons/left-thumb.png differ
diff --git a/panels/user-accounts/data/icons/left-thumb.svg b/panels/user-accounts/data/icons/left-thumb.svg
new file mode 100644
index 000000000..fd0f5827e
--- /dev/null
+++ b/panels/user-accounts/data/icons/left-thumb.svg
@@ -0,0 +1,180 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/print_error.png b/panels/user-accounts/data/icons/print_error.png
new file mode 100644
index 000000000..d67150b1b
Binary files /dev/null and b/panels/user-accounts/data/icons/print_error.png differ
diff --git a/panels/user-accounts/data/icons/print_error.svg b/panels/user-accounts/data/icons/print_error.svg
new file mode 100644
index 000000000..4ad6beedc
--- /dev/null
+++ b/panels/user-accounts/data/icons/print_error.svg
@@ -0,0 +1,525 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/print_ok.png b/panels/user-accounts/data/icons/print_ok.png
new file mode 100644
index 000000000..4dd615e72
Binary files /dev/null and b/panels/user-accounts/data/icons/print_ok.png differ
diff --git a/panels/user-accounts/data/icons/print_ok.svg b/panels/user-accounts/data/icons/print_ok.svg
new file mode 100644
index 000000000..ba821ef71
--- /dev/null
+++ b/panels/user-accounts/data/icons/print_ok.svg
@@ -0,0 +1,310 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-index-finger.png b/panels/user-accounts/data/icons/right-index-finger.png
new file mode 100644
index 000000000..4aaeaac40
Binary files /dev/null and b/panels/user-accounts/data/icons/right-index-finger.png differ
diff --git a/panels/user-accounts/data/icons/right-index-finger.svg b/panels/user-accounts/data/icons/right-index-finger.svg
new file mode 100644
index 000000000..5a621a2e1
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-index-finger.svg
@@ -0,0 +1,179 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-little-finger.png b/panels/user-accounts/data/icons/right-little-finger.png
new file mode 100644
index 000000000..17946afc7
Binary files /dev/null and b/panels/user-accounts/data/icons/right-little-finger.png differ
diff --git a/panels/user-accounts/data/icons/right-little-finger.svg b/panels/user-accounts/data/icons/right-little-finger.svg
new file mode 100644
index 000000000..9fcec2af3
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-little-finger.svg
@@ -0,0 +1,182 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-middle-finger.png b/panels/user-accounts/data/icons/right-middle-finger.png
new file mode 100644
index 000000000..71bd41ba0
Binary files /dev/null and b/panels/user-accounts/data/icons/right-middle-finger.png differ
diff --git a/panels/user-accounts/data/icons/right-middle-finger.svg b/panels/user-accounts/data/icons/right-middle-finger.svg
new file mode 100644
index 000000000..b33a654a9
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-middle-finger.svg
@@ -0,0 +1,182 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-ring-finger.png b/panels/user-accounts/data/icons/right-ring-finger.png
new file mode 100644
index 000000000..aa73ae6ea
Binary files /dev/null and b/panels/user-accounts/data/icons/right-ring-finger.png differ
diff --git a/panels/user-accounts/data/icons/right-ring-finger.svg b/panels/user-accounts/data/icons/right-ring-finger.svg
new file mode 100644
index 000000000..9e264fe60
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-ring-finger.svg
@@ -0,0 +1,182 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/icons/right-thumb.png b/panels/user-accounts/data/icons/right-thumb.png
new file mode 100644
index 000000000..1c967d611
Binary files /dev/null and b/panels/user-accounts/data/icons/right-thumb.png differ
diff --git a/panels/user-accounts/data/icons/right-thumb.svg b/panels/user-accounts/data/icons/right-thumb.svg
new file mode 100644
index 000000000..0aa0f2e48
--- /dev/null
+++ b/panels/user-accounts/data/icons/right-thumb.svg
@@ -0,0 +1,182 @@
+
+
+
\ No newline at end of file
diff --git a/panels/user-accounts/data/language-chooser.ui b/panels/user-accounts/data/language-chooser.ui
new file mode 100644
index 000000000..b61e24bc4
--- /dev/null
+++ b/panels/user-accounts/data/language-chooser.ui
@@ -0,0 +1,90 @@
+
+
+
+ 400
+ 5
+ True
+ center-on-parent
+ dialog
+
+ system-users
+
+
+ True
+ vertical
+ 2
+
+
+ True
+ 10
+ 10
+ 10
+ 10
+
+
+ True
+ True
+ never
+ automatic
+ in
+
+
+ True
+ True
+ False
+ False
+
+
+
+
+
+
+ 1
+
+
+
+
+ True
+ end
+
+
+ Cancel
+ True
+ True
+ True
+
+
+ False
+ False
+ 0
+
+
+
+
+ Select
+ True
+ True
+ True
+ True
+
+
+ False
+ False
+ 1
+
+
+
+
+ False
+ end
+ 0
+
+
+
+
+
+ cancel-button
+ ok-button
+
+
+
diff --git a/panels/user-accounts/data/password-dialog.ui b/panels/user-accounts/data/password-dialog.ui
new file mode 100644
index 000000000..69e9d6fa2
--- /dev/null
+++ b/panels/user-accounts/data/password-dialog.ui
@@ -0,0 +1,531 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Set a password now
+ 0
+
+
+ Choose password at next login
+ 1
+
+
+ Log in without a password
+ 2
+
+
+ Disable this account
+ 3
+
+
+ Enable this account
+ 4
+
+
+
+
+ 5
+
+ False
+ True
+ center-on-parent
+ system-users
+ dialog
+
+
+ True
+ vertical
+ 12
+
+
+ True
+ 6
+ vertical
+ 16
+
+
+ True
+ 8
+ 2
+ 10
+ 6
+
+
+ True
+ vertical
+
+
+ True
+ 1
+ _Hint:
+ True
+ normal-hint-entry
+
+
+
+
+
+ 0
+
+
+
+
+ True
+
+
+ 1
+
+
+
+
+ 7
+ 8
+ GTK_FILL
+
+
+
+
+ True
+ vertical
+
+
+ True
+ True
+
+
+ 0
+
+
+
+
+ True
+ 0
+ <small>This hint may be displayed at the login screen. It will be visible to all users of this system. Do <b>not</b> include the password here.</small>
+ True
+ True
+
+
+ 1
+
+
+
+
+ 1
+ 2
+ 7
+ 8
+
+
+
+
+ True
+ True
+ False
+
+
+ 1
+ 2
+ 5
+ 6
+
+
+
+
+ True
+ 1
+ C_onfirm password:
+ True
+ verify-entry
+
+
+
+
+
+ 5
+ 6
+ GTK_FILL
+
+
+
+
+ True
+ vertical
+
+
+ True
+ 1
+ _New password:
+ True
+
+
+
+
+
+ 0
+
+
+
+
+ True
+
+
+ 1
+
+
+
+
+ 4
+ 5
+ GTK_FILL
+
+
+
+
+ True
+ vertical
+ 6
+
+
+ True
+ 6
+
+
+ True
+ True
+ False
+
+
+ 0
+
+
+
+
+ True
+ True
+ False
+ Choose a generated password
+
+
+ True
+ gtk-execute
+
+
+
+
+ False
+ False
+ 1
+
+
+
+
+ 0
+
+
+
+
+ True
+ 9
+
+
+ True
+ 6
+ 6
+
+
+ True
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ True
+ 0
+ Fair
+
+
+ 1
+
+
+
+
+ 1
+
+
+
+
+ 1
+ 2
+ 4
+ 5
+
+
+
+
+ True
+ 1
+ Current _password:
+ True
+ old-password-entry
+
+
+
+
+
+ 3
+ 4
+
+
+
+
+ True
+ True
+ False
+
+
+ 1
+ 2
+ 3
+ 4
+
+
+
+
+ True
+ 1
+ _Action:
+ True
+ action-combo
+
+
+
+
+
+ 2
+ 3
+
+
+
+
+ True
+ action-model
+
+
+
+ 0
+
+
+
+
+ 1
+ 2
+ 2
+ 3
+
+
+
+
+ True
+
+
+ 1
+ 2
+
+
+
+
+ True
+
+
+ 1
+ 2
+ 1
+ 2
+
+
+
+
+ True
+
+
+ True
+
+
+ 0
+
+
+
+
+ True
+
+
+ False
+ 1
+
+
+
+
+ GTK_FILL
+
+
+
+
+ True
+ vertical
+
+
+ True
+ 0
+ Changing password for:
+
+
+ 0
+
+
+
+
+ True
+ 0
+
+
+
+
+
+
+ 1
+
+
+
+
+ 1
+ 2
+
+
+
+
+ _Show password
+ True
+ True
+ False
+ True
+ True
+
+
+ 1
+ 2
+ 6
+ 7
+
+
+
+
+
+
+
+ 0
+
+
+
+
+ False
+ False
+ 1
+
+
+
+
+ True
+ edge
+
+
+ True
+ 0
+
+
+ True
+ True
+ True
+ <a href="http://wolfram.org/writing/howto/password.html">How to choose a strong password</a>
+ True
+ False
+
+
+
+
+ False
+ False
+ 0
+ True
+
+
+
+
+ True
+ 6
+ end
+
+
+ Ch_ange
+ True
+ True
+ True
+ True
+ True
+
+
+ False
+ False
+ end
+ 1
+
+
+
+
+ gtk-cancel
+ True
+ True
+ True
+ True
+
+
+ False
+ False
+ end
+ 0
+
+
+
+
+ False
+ False
+ 1
+ True
+
+
+
+
+ False
+ end
+ 0
+
+
+
+
+
+
+
diff --git a/panels/user-accounts/data/photo-dialog.ui b/panels/user-accounts/data/photo-dialog.ui
new file mode 100644
index 000000000..5bc10d8d0
--- /dev/null
+++ b/panels/user-accounts/data/photo-dialog.ui
@@ -0,0 +1,304 @@
+
+
+
+ 0
+ 100
+ 0
+ 1
+ 10
+ 50
+
+
+ 5
+ GTK_WIN_POS_CENTER_ON_PARENT
+ GDK_WINDOW_TYPE_HINT_DIALOG
+
+ system-users
+ True
+
+
+ True
+ 6
+
+
+ True
+ 10
+ 10
+
+
+ True
+ 10
+
+
+ True
+ 6
+
+
+ True
+
+
+ False
+ False
+
+
+
+
+ True
+
+
+ True
+ 0
+ Changing photo for:
+
+
+
+
+ True
+ 0
+
+
+
+
+
+
+
+
+
+
+ False
+ False
+
+
+
+
+ True
+
+
+ 1
+
+
+
+
+ True
+ 10
+
+
+ True
+ 0
+ 0
+ Choose a picture that will be shown at the login screen for this account.
+ GTK_JUSTIFY_CENTER
+ True
+ 28
+
+
+
+
+ 2
+
+
+
+
+ True
+ 5
+
+
+ True
+ True
+ True
+ Gallery
+ False
+
+
+
+
+ True
+ True
+ True
+ Browse for more pictures
+ False
+ gallery-radiobutton
+
+
+
+
+ True
+ True
+ True
+ Take a photograph
+ False
+ gallery-radiobutton
+
+
+
+
+ False
+ False
+ 3
+
+
+
+
+ False
+ False
+
+
+
+
+ True
+ True
+ False
+
+
+ True
+ 5
+
+
+ True
+ never
+ automatic
+
+
+ True
+
+
+
+
+
+
+
+
+ True
+ Gallery
+
+
+ False
+
+
+
+
+ True
+ False
+
+
+ True
+
+
+ True
+ True
+
+
+
+
+ False
+ False
+
+
+ True
+ avatar-default
+ 10
+
+
+ False
+ False
+
+
+
+
+ True
+ False
+ browse-scale-adjustment
+
+
+ True
+ True
+
+
+
+
+ True
+ avatar-default
+ 20
+
+
+ False
+ False
+
+
+
+
+ True
+ False
+
+
+
+
+
+
+ True
+ Browse
+
+
+ 1
+ False
+
+
+
+
+ True
+
+
+
+
+ True
+ Photograph
+
+
+ 2
+ False
+
+
+
+
+ 1
+
+
+
+
+ 1
+
+
+
+
+ True
+ GTK_BUTTONBOX_END
+
+
+ True
+ True
+ True
+ Cancel
+
+
+
+
+ True
+ True
+ True
+ True
+ Select
+
+
+ 1
+
+
+
+
+ False
+ GTK_PACK_END
+
+
+
+
+
+
diff --git a/panels/user-accounts/data/user-accounts-dialog.ui b/panels/user-accounts/data/user-accounts-dialog.ui
new file mode 100644
index 000000000..e6e4f8cd7
--- /dev/null
+++ b/panels/user-accounts/data/user-accounts-dialog.ui
@@ -0,0 +1,726 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Standard
+ 0
+
+
+ Administrator
+ 1
+
+
+ Supervised
+ 2
+
+
+
+
+
+
+
+
+
+
+
+
+ 12
+ list-add
+
+
+ 12
+ list-remove
+
+
+ 12
+ Account Information
+ system-users
+
+
+ True
+ 6
+
+
+ True
+ True
+ True
+ 5
+
+
+ True
+ 12
+ 12
+
+
+ True
+ vertical
+ 2
+
+
+ True
+ True
+ never
+ automatic
+ in
+
+
+ True
+ True
+ False
+
+
+
+
+ 0
+
+
+
+
+ True
+ 2
+
+
+ True
+ True
+ True
+ Create a user
+ plus
+
+
+ False
+ False
+ 0
+
+
+
+
+ True
+ True
+ True
+ Delete the selected user
+ minus
+
+
+ False
+ False
+ 1
+
+
+
+
+ False
+ False
+ 1
+
+
+
+
+ 0
+
+
+
+
+ True
+ vertical
+ 6
+
+
+ True
+
+
+ True
+ 8
+ 2
+ 10
+
+
+ True
+
+
+ Open
+ True
+ True
+ True
+ 0
+
+
+ False
+ False
+ 0
+
+
+
+
+
+
+
+ 1
+ 2
+ 7
+ 8
+
+
+
+
+
+
+ Open
+ True
+ True
+ True
+ 0
+
+
+ False
+ False
+ 0
+
+
+
+
+ 1
+ 2
+ 7
+ 8
+
+
+
+
+ 1
+ Address Book Card:
+
+
+
+
+
+ 7
+ 8
+ GTK_FILL
+
+
+
+
+ True
+
+
+ 1
+ 2
+ 5
+ 6
+
+
+
+
+ True
+ False
+ False
+
+
+ True
+ 0
+
+
+
+
+ True
+ True
+ True
+ none
+
+
+ True
+ 0
+
+
+
+
+ 1
+
+
+
+
+ 1
+ 2
+ 6
+ 7
+
+
+
+
+ True
+ account-type-model
+ 0
+
+
+ 1
+ 2
+ 1
+ 2
+
+
+
+
+ True
+ 1
+ Account type:
+
+
+
+
+
+ 1
+ 2
+ GTK_FILL
+
+
+
+
+ True
+ vertical
+
+
+ True
+
+
+ 0
+
+
+
+
+ True
+ 1.2
+ 700
+
+
+ 1
+
+
+
+
+ 1
+ 2
+
+
+
+
+ True
+ 1
+ Password:
+
+
+
+
+
+ 2
+ 3
+ GTK_FILL
+
+
+
+
+ True
+
+
+ 1
+ 2
+ 2
+ 3
+
+
+
+
+ True
+ 1
+ E-mail address:
+
+
+
+
+
+ 3
+ 4
+ GTK_FILL
+
+
+
+
+ True
+
+
+ 1
+ 2
+ 3
+ 4
+
+
+
+
+ True
+ 1
+ Language:
+
+
+
+
+
+ 4
+ 5
+ GTK_FILL
+
+
+
+
+ True
+ 1
+ Location:
+
+
+
+
+
+ 5
+ 6
+ GTK_FILL
+
+
+
+
+ True
+ 1
+ Fingerprint Login:
+
+
+
+
+
+ 6
+ 7
+ GTK_FILL
+
+
+
+
+ True
+ 1
+ Restrictions:
+
+
+
+
+
+ 7
+ 8
+ GTK_FILL
+
+
+
+
+ True
+
+
+ True
+
+
+ 0
+
+
+
+
+ 0
+ 0
+
+
+ True
+ avatar-default
+ 6
+
+
+
+
+ False
+ False
+ 1
+
+
+
+
+ True
+ True
+ True
+ none
+
+
+ True
+ avatar-default
+ 6
+
+
+
+
+ False
+ False
+ 2
+
+
+
+
+ GTK_FILL
+
+
+
+
+ True
+ language-model
+ 1
+
+
+ 1
+ 2
+ 4
+ 5
+ GTK_FILL
+
+
+
+
+ False
+ 0
+
+
+
+
+
+
+
+ False
+ 0
+
+
+
+
+ 1
+
+
+
+
+
+
+ True
+ Accounts
+ center
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+ True
+ vertical
+ 6
+ 12
+
+
+ True
+ 6
+
+
+ True
+ 0
+ Automatic Login:
+
+
+ False
+ False
+ 0
+
+
+
+
+ True
+
+
+
+ 0
+
+
+
+
+ 1
+
+
+
+
+ False
+ False
+ 0
+
+
+
+
+ Show list of users
+ True
+ True
+ False
+ True
+ True
+
+
+ False
+ False
+ 1
+
+
+
+
+ Show Shutdown, Suspend and Restart actions
+ True
+ True
+ False
+ True
+ True
+
+
+ False
+ False
+ 2
+
+
+
+
+ Show password hints
+ True
+ True
+ False
+ True
+ True
+
+
+ False
+ False
+ 3
+
+
+
+
+ True
+ 0
+ 0
+ A guest account will allow anyone to temporarily log in to this computer without a password. For security, remote logins to this account are not allowed.
+
+<b>When the guest user logs out, all files and data associated with the account will be deleted.</b>
+ True
+ True
+
+
+ False
+ False
+ 4
+
+
+
+
+ Allow guests to log in to this computer
+ True
+ True
+ False
+ True
+ True
+
+
+ False
+ False
+ 5
+
+
+
+
+ True
+
+
+ True
+ 0
+ 1
+ 0
+ 0
+
+
+
+
+
+ 0
+
+
+
+
+ 6
+
+
+
+
+ 2
+
+
+
+
+ True
+ Login Options
+ center
+
+
+ False
+
+
+
+
+
+
+
+
+
+
+ 0
+
+
+
+
+
+
+ both
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/panels/user-accounts/fingerprint-strings.h b/panels/user-accounts/fingerprint-strings.h
new file mode 100644
index 000000000..6fb7cb31a
--- /dev/null
+++ b/panels/user-accounts/fingerprint-strings.h
@@ -0,0 +1,111 @@
+/*
+ * Helper functions to translate statuses and actions to strings
+ * Copyright (C) 2008 Bastien Nocera
+ *
+ * Experimental code. This will be moved out of fprintd into it's own
+ * package once the system has matured.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+struct {
+ const char *dbus_name;
+ const char *place_str;
+ const char *swipe_str;
+} fingers[11] = {
+ { "left-thumb", N_("Place your left thumb on %s"), N_("Swipe your left thumb on %s") },
+ { "left-index-finger", N_("Place your left index finger on %s"), N_("Swipe your left index finger on %s") },
+ { "left-middle-finger", N_("Place your left middle finger on %s"), N_("Swipe your left middle finger on %s") },
+ { "left-ring-finger", N_("Place your left ring finger on %s"), N_("Swipe your left ring finger on %s") },
+ { "left-little-finger", N_("Place your left little finger on %s"), N_("Swipe your left little finger on %s") },
+ { "right-thumb", N_("Place your right thumb on %s"), N_("Swipe your right thumb on %s") },
+ { "right-index-finger", N_("Place your right index finger on %s"), N_("Swipe your right index finger on %s") },
+ { "right-middle-finger", N_("Place your right middle finger on %s"), N_("Swipe your right middle finger on %s") },
+ { "right-ring-finger", N_("Place your right ring finger on %s"), N_("Swipe your right ring finger on %s") },
+ { "right-little-finger", N_("Place your right little finger on %s"), N_("Swipe your right little finger on %s") },
+ { NULL, NULL, NULL }
+};
+
+static const char *finger_str_to_msg(const char *finger_name, gboolean is_swipe)
+{
+ int i;
+
+ if (finger_name == NULL)
+ return NULL;
+
+ for (i = 0; fingers[i].dbus_name != NULL; i++) {
+ if (g_str_equal (fingers[i].dbus_name, finger_name)) {
+ if (is_swipe == FALSE)
+ return fingers[i].place_str;
+ else
+ return fingers[i].swipe_str;
+ }
+ }
+
+ return NULL;
+}
+
+/* Cases not handled:
+ * verify-no-match
+ * verify-match
+ * verify-unknown-error
+ */
+static const char *verify_result_str_to_msg(const char *result, gboolean is_swipe)
+{
+ if (result == NULL)
+ return NULL;
+
+ if (strcmp (result, "verify-retry-scan") == 0) {
+ if (is_swipe == FALSE)
+ return N_("Place your finger on the reader again");
+ else
+ return N_("Swipe your finger again");
+ }
+ if (strcmp (result, "verify-swipe-too-short") == 0)
+ return N_("Swipe was too short; try again");
+ if (strcmp (result, "verify-finger-not-centered") == 0)
+ return N_("Your finger was not centered; try swiping your finger again");
+ if (strcmp (result, "verify-remove-and-retry") == 0)
+ return N_("Remove your finger and try swiping it again");
+
+ return NULL;
+}
+
+/* Cases not handled:
+ * enroll-completed
+ * enroll-failed
+ * enroll-unknown-error
+ */
+static const char *enroll_result_str_to_msg(const char *result, gboolean is_swipe)
+{
+ if (result == NULL)
+ return NULL;
+
+ if (strcmp (result, "enroll-retry-scan") == 0 || strcmp (result, "enroll-stage-passed") == 0) {
+ if (is_swipe == FALSE)
+ return N_("Place your finger on the reader again");
+ else
+ return N_("Swipe your finger again");
+ }
+ if (strcmp (result, "enroll-swipe-too-short") == 0)
+ return N_("Swipe was too short, try again");
+ if (strcmp (result, "enroll-finger-not-centered") == 0)
+ return N_("Your finger was not centered, try swiping your finger again");
+ if (strcmp (result, "enroll-remove-and-retry") == 0)
+ return N_("Remove your finger, and try swiping your finger again");
+
+ return NULL;
+}
+
diff --git a/panels/user-accounts/fprintd-marshal.list b/panels/user-accounts/fprintd-marshal.list
new file mode 100644
index 000000000..c4effb63a
--- /dev/null
+++ b/panels/user-accounts/fprintd-marshal.list
@@ -0,0 +1 @@
+VOID:STRING,BOOLEAN
diff --git a/panels/user-accounts/gdm-languages.c b/panels/user-accounts/gdm-languages.c
new file mode 100644
index 000000000..2c99e1529
--- /dev/null
+++ b/panels/user-accounts/gdm-languages.c
@@ -0,0 +1,1003 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2008 Red Hat, Inc,
+ * 2007 William Jon McCann
+ *
+ * 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.
+ *
+ * Written by : William Jon McCann
+ * Ray Strode
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "gdm-languages.h"
+
+#include
+#ifndef __LC_LAST
+#define __LC_LAST 13
+#endif
+#include "locarchive.h"
+
+#define ALIASES_FILE LIBLOCALEDIR "/locale.alias"
+#define ARCHIVE_FILE LIBLOCALEDIR "/locale-archive"
+#define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
+#define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
+
+typedef struct _GdmLocale {
+ char *id;
+ char *name;
+ char *language_code;
+ char *territory_code;
+ char *codeset;
+ char *modifier;
+} GdmLocale;
+
+static GHashTable *gdm_languages_map;
+static GHashTable *gdm_territories_map;
+static GHashTable *gdm_available_locales_map;
+
+static void
+gdm_locale_free (GdmLocale *locale)
+{
+ if (locale == NULL) {
+ return;
+ }
+
+ g_free (locale->id);
+ g_free (locale->name);
+ g_free (locale->codeset);
+ g_free (locale->modifier);
+ g_free (locale);
+}
+
+static char *
+normalize_codeset (const char *codeset)
+{
+ char *normalized_codeset;
+ const char *p;
+ char *q;
+
+ normalized_codeset = g_strdup (codeset);
+
+ if (codeset != NULL) {
+ for (p = codeset, q = normalized_codeset;
+ *p != '\0'; p++) {
+
+ if (*p == '-' || *p == '_') {
+ continue;
+ }
+
+ *q = g_ascii_tolower (*p);
+ q++;
+ }
+ *q = '\0';
+ }
+
+ return normalized_codeset;
+}
+
+/*
+ * According to http://en.wikipedia.org/wiki/Locale
+ * locale names are of the form:
+ * [language[_territory][.codeset][@modifier]]
+ */
+gboolean
+gdm_parse_language_name (const char *name,
+ char **language_codep,
+ char **territory_codep,
+ char **codesetp,
+ char **modifierp)
+{
+ GRegex *re;
+ GMatchInfo *match_info;
+ gboolean res;
+ GError *error;
+
+ error = NULL;
+ re = g_regex_new ("^(?P[^_.@[:space:]]+)"
+ "(_(?P[[:upper:]]+))?"
+ "(\\.(?P[-_0-9a-zA-Z]+))?"
+ "(@(?P[[:ascii:]]+))?$",
+ 0, 0, &error);
+ if (re == NULL) {
+ g_critical ("%s", error->message);
+ return FALSE;
+ }
+
+ if (!g_regex_match (re, name, 0, &match_info) ||
+ g_match_info_is_partial_match (match_info)) {
+ g_match_info_free (match_info);
+ g_regex_unref (re);
+ g_warning ("locale %s isn't valid\n", name);
+ return FALSE;
+ }
+
+ res = g_match_info_matches (match_info);
+ if (! res) {
+ g_match_info_free (match_info);
+ g_regex_unref (re);
+ g_warning ("Unable to parse locale: %s", name);
+ return FALSE;
+ }
+
+ if (language_codep != NULL) {
+ *language_codep = g_match_info_fetch_named (match_info, "language");
+ }
+
+ if (territory_codep != NULL) {
+ *territory_codep = g_match_info_fetch_named (match_info, "territory");
+
+ if (*territory_codep != NULL &&
+ *territory_codep[0] == '\0') {
+ g_free (*territory_codep);
+ *territory_codep = NULL;
+ }
+ }
+
+ if (codesetp != NULL) {
+ *codesetp = g_match_info_fetch_named (match_info, "codeset");
+
+ if (*codesetp != NULL &&
+ *codesetp[0] == '\0') {
+ g_free (*codesetp);
+ *codesetp = NULL;
+ }
+
+ if (*codesetp != NULL) {
+ char *codeset;
+
+ codeset = normalize_codeset (*codesetp);
+ g_free (*codesetp);
+ *codesetp = codeset;
+ }
+ }
+
+ if (modifierp != NULL) {
+ *modifierp = g_match_info_fetch_named (match_info, "modifier");
+
+ if (*modifierp != NULL &&
+ *modifierp[0] == '\0') {
+ g_free (*modifierp);
+ *modifierp = NULL;
+ }
+ }
+
+ g_match_info_free (match_info);
+ g_regex_unref (re);
+
+ return TRUE;
+}
+
+static char *
+construct_language_name (const char *language,
+ const char *territory,
+ const char *codeset,
+ const char *modifier)
+{
+ char *name;
+
+ g_assert (language[0] != 0);
+ g_assert (territory == NULL || territory[0] != 0);
+ g_assert (codeset == NULL || codeset[0] != 0);
+ g_assert (modifier == NULL || modifier[0] != 0);
+
+ name = g_strdup_printf ("%s%s%s%s%s%s%s",
+ language,
+ territory != NULL? "_" : "",
+ territory != NULL? territory : "",
+ codeset != NULL? "." : "",
+ codeset != NULL? codeset : "",
+ modifier != NULL? "@" : "",
+ modifier != NULL? modifier : "");
+
+ return name;
+}
+
+static void
+make_codeset_canonical_for_locale (const char *name,
+ char **codeset)
+{
+ char *old_locale;
+
+ old_locale = setlocale (LC_CTYPE, NULL);
+
+ if (setlocale (LC_CTYPE, name) == NULL) {
+ return;
+ }
+
+ g_free (*codeset);
+ *codeset = g_strdup (nl_langinfo (CODESET));
+
+ setlocale (LC_CTYPE, old_locale);
+}
+
+char *
+gdm_normalize_language_name (const char *name)
+{
+ char *normalized_name;
+ char *language_code;
+ char *territory_code;
+ char *codeset;
+ char *modifier;
+
+ if (name[0] == '\0') {
+ return NULL;
+ }
+
+ gdm_parse_language_name (name,
+ &language_code,
+ &territory_code,
+ &codeset, &modifier);
+
+ if (codeset != NULL) {
+ make_codeset_canonical_for_locale (name, &codeset);
+ }
+
+ normalized_name = construct_language_name (language_code,
+ territory_code,
+ codeset, modifier);
+ g_free (language_code);
+ g_free (territory_code);
+ g_free (codeset);
+ g_free (modifier);
+
+ return normalized_name;
+}
+
+static gboolean
+language_name_is_valid (const char *language_name)
+{
+ char *old_locale;
+ gboolean is_valid;
+
+ old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
+ is_valid = setlocale (LC_MESSAGES, language_name) != NULL;
+ setlocale (LC_MESSAGES, old_locale);
+ g_free (old_locale);
+
+ return is_valid;
+}
+
+static gboolean
+language_name_is_utf8 (const char *language_name)
+{
+ char *old_locale;
+ char *codeset;
+ gboolean is_utf8;
+
+ old_locale = g_strdup (setlocale (LC_CTYPE, NULL));
+
+ if (setlocale (LC_CTYPE, language_name) == NULL) {
+ g_free (old_locale);
+ return FALSE;
+ }
+
+ codeset = normalize_codeset (nl_langinfo (CODESET));
+
+ is_utf8 = strcmp (codeset, "utf8") == 0;
+ g_free (codeset);
+
+ setlocale (LC_CTYPE, old_locale);
+ g_free (old_locale);
+
+ return is_utf8;
+}
+
+static gboolean
+language_name_has_translations (const char *language_name)
+{
+ GDir *dir;
+ char *path;
+ const char *name;
+ gboolean has_translations;
+
+ path = g_build_filename (GNOMELOCALEDIR, language_name, "LC_MESSAGES", NULL);
+
+ has_translations = FALSE;
+ dir = g_dir_open (path, 0, NULL);
+ g_free (path);
+
+ if (dir == NULL) {
+ goto out;
+ }
+
+ do {
+ name = g_dir_read_name (dir);
+
+ if (name == NULL) {
+ break;
+ }
+
+ if (g_str_has_suffix (name, ".mo")) {
+ has_translations = TRUE;
+ break;
+ }
+ } while (name != NULL);
+ g_dir_close (dir);
+
+out:
+ return has_translations;
+}
+
+static gboolean
+add_locale (const char *language_name)
+{
+ GdmLocale *locale;
+ GdmLocale *old_locale;
+ char *name;
+
+ if (language_name_is_utf8 (language_name)) {
+ name = g_strdup (language_name);
+ } else {
+ name = g_strdup_printf ("%s.utf8", language_name);
+
+ if (!language_name_is_utf8 (name)) {
+ g_free (name);
+ return FALSE;
+ }
+ }
+
+ if (!language_name_is_valid (name)) {
+ g_free (name);
+ return FALSE;
+ }
+
+
+ locale = g_new0 (GdmLocale, 1);
+ gdm_parse_language_name (name,
+ &locale->language_code,
+ &locale->territory_code,
+ &locale->codeset,
+ &locale->modifier);
+ g_free (name);
+ name = NULL;
+
+ locale->id = construct_language_name (locale->language_code, locale->territory_code,
+ NULL, locale->modifier);
+ locale->name = construct_language_name (locale->language_code, locale->territory_code,
+ locale->codeset, locale->modifier);
+
+ if (!language_name_has_translations (locale->name) &&
+ !language_name_has_translations (locale->id) &&
+ !language_name_has_translations (locale->language_code)) {
+ gdm_locale_free (locale);
+ return FALSE;
+ }
+
+ old_locale = g_hash_table_lookup (gdm_available_locales_map, locale->id);
+ if (old_locale != NULL) {
+ if (strlen (old_locale->name) > strlen (locale->name)) {
+ gdm_locale_free (locale);
+ return FALSE;
+ }
+ }
+
+ g_hash_table_insert (gdm_available_locales_map, g_strdup (locale->id), locale);
+
+ return TRUE;
+}
+
+struct nameent
+{
+ char *name;
+ uint32_t locrec_offset;
+};
+
+static gboolean
+collect_locales_from_archive (void)
+{
+ GMappedFile *mapped;
+ GError *error;
+ char *addr;
+ struct locarhead *head;
+ struct namehashent *namehashtab;
+ struct nameent *names;
+ uint32_t used;
+ uint32_t cnt;
+ gsize len;
+ gboolean locales_collected;
+
+ error = NULL;
+ mapped = g_mapped_file_new (ARCHIVE_FILE, FALSE, &error);
+ if (mapped == NULL) {
+ g_warning ("Mapping failed for %s: %s", ARCHIVE_FILE, error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ locales_collected = FALSE;
+
+ addr = g_mapped_file_get_contents (mapped);
+ len = g_mapped_file_get_length (mapped);
+
+ head = (struct locarhead *) addr;
+ if (head->namehash_offset + head->namehash_size > len
+ || head->string_offset + head->string_size > len
+ || head->locrectab_offset + head->locrectab_size > len
+ || head->sumhash_offset + head->sumhash_size > len) {
+ goto out;
+ }
+
+ namehashtab = (struct namehashent *) (addr + head->namehash_offset);
+
+ names = (struct nameent *) g_new0 (struct nameent, head->namehash_used);
+ for (cnt = used = 0; cnt < head->namehash_size; ++cnt) {
+ if (namehashtab[cnt].locrec_offset != 0) {
+ names[used].name = addr + namehashtab[cnt].name_offset;
+ names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
+ }
+ }
+
+ for (cnt = 0; cnt < used; ++cnt) {
+ add_locale (names[cnt].name);
+ }
+
+ g_free (names);
+
+ locales_collected = TRUE;
+ out:
+
+ g_mapped_file_unref (mapped);
+ return locales_collected;
+}
+
+static int
+select_dirs (const struct dirent *dirent)
+{
+ int result = 0;
+
+ if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0) {
+ mode_t mode = 0;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK) {
+ mode = DTTOIF (dirent->d_type);
+ } else
+#endif
+ {
+ struct stat st;
+ char *path;
+
+ path = g_build_filename (LIBLOCALEDIR, dirent->d_name, NULL);
+ if (g_stat (path, &st) == 0) {
+ mode = st.st_mode;
+ }
+ g_free (path);
+ }
+
+ result = S_ISDIR (mode);
+ }
+
+ return result;
+}
+
+static void
+collect_locales_from_directory (void)
+{
+ struct dirent **dirents;
+ int ndirents;
+ int cnt;
+
+ ndirents = scandir (LIBLOCALEDIR, &dirents, select_dirs, alphasort);
+
+ for (cnt = 0; cnt < ndirents; ++cnt) {
+ add_locale (dirents[cnt]->d_name);
+ }
+
+ if (ndirents > 0) {
+ free (dirents);
+ }
+}
+
+static void
+collect_locales (void)
+{
+
+ if (gdm_available_locales_map == NULL) {
+ gdm_available_locales_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gdm_locale_free);
+ }
+
+ if (collect_locales_from_archive ()) {
+ return;
+ } else {
+ g_warning ("Could not read list of available locales from libc, "
+ "guessing possible locales from available translations, "
+ "but list may be incomplete!");
+
+ collect_locales_from_directory ();
+ }
+}
+
+static gboolean
+is_fallback_language (const char *code)
+{
+ const char *fallback_language_names[] = { "C", "POSIX", NULL };
+ int i;
+
+ for (i = 0; fallback_language_names[i] != NULL; i++) {
+ if (strcmp (code, fallback_language_names[i]) == 0) {
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static const char *
+get_language (const char *code)
+{
+ const char *name;
+ int len;
+
+ g_assert (code != NULL);
+
+ if (is_fallback_language (code)) {
+ return "Unspecified";
+ }
+
+ len = strlen (code);
+ if (len != 2 && len != 3) {
+ return NULL;
+ }
+
+ name = (const char *) g_hash_table_lookup (gdm_languages_map, code);
+
+ return name;
+}
+
+static char *
+get_first_item_in_semicolon_list (const char *list)
+{
+ char **items;
+ char *item;
+
+ /* Some entries in iso codes have multiple values, separated
+ * by semicolons. Not really sure which one to pick, so
+ * we just arbitrarily pick the first one.
+ */
+ items = g_strsplit (list, "; ", 2);
+
+ item = g_strdup (items[0]);
+ g_strfreev (items);
+
+ return item;
+}
+
+static char *
+get_translated_language (const char *code,
+ const char *locale)
+{
+ const char *language;
+ char *name;
+
+ language = get_language (code);
+
+ name = NULL;
+ if (language != NULL) {
+ const char *translated_name;
+ char *old_locale;
+
+ if (locale != NULL) {
+ old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
+ setlocale (LC_MESSAGES, locale);
+ }
+
+ if (is_fallback_language (code)) {
+ name = _("Unspecified");
+ } else {
+ translated_name = dgettext ("iso_639", language);
+ name = get_first_item_in_semicolon_list (translated_name);
+ }
+
+ if (locale != NULL) {
+ setlocale (LC_MESSAGES, old_locale);
+ g_free (old_locale);
+ }
+ }
+
+ return name;
+}
+
+static const char *
+get_territory (const char *code)
+{
+ const char *name;
+ int len;
+
+ g_assert (code != NULL);
+
+ len = strlen (code);
+ if (len != 2 && len != 3) {
+ return NULL;
+ }
+
+ name = (const char *) g_hash_table_lookup (gdm_territories_map, code);
+
+ return name;
+}
+
+static const char *
+get_translated_territory (const char *code,
+ const char *locale)
+{
+ const char *territory;
+ char *name;
+
+ territory = get_territory (code);
+
+ name = NULL;
+ if (territory != NULL) {
+ const char *translated_territory;
+ char *old_locale;
+
+ if (locale != NULL) {
+ old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
+ setlocale (LC_MESSAGES, locale);
+ }
+
+ translated_territory = dgettext ("iso_3166", territory);
+ name = get_first_item_in_semicolon_list (translated_territory);
+
+ if (locale != NULL) {
+ setlocale (LC_MESSAGES, old_locale);
+ g_free (old_locale);
+ }
+ }
+
+ return name;
+}
+
+static void
+languages_parse_start_tag (GMarkupParseContext *ctx,
+ const char *element_name,
+ const char **attr_names,
+ const char **attr_values,
+ gpointer user_data,
+ GError **error)
+{
+ const char *ccode_longB;
+ const char *ccode_longT;
+ const char *ccode;
+ const char *lang_name;
+
+ if (! g_str_equal (element_name, "iso_639_entry") || attr_names == NULL || attr_values == NULL) {
+ return;
+ }
+
+ ccode = NULL;
+ ccode_longB = NULL;
+ ccode_longT = NULL;
+ lang_name = NULL;
+
+ while (*attr_names && *attr_values) {
+ if (g_str_equal (*attr_names, "iso_639_1_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 2) {
+ return;
+ }
+ ccode = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 3) {
+ return;
+ }
+ ccode_longB = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 3) {
+ return;
+ }
+ ccode_longT = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "name")) {
+ lang_name = *attr_values;
+ }
+
+ ++attr_names;
+ ++attr_values;
+ }
+
+ if (lang_name == NULL) {
+ return;
+ }
+
+ if (ccode != NULL) {
+ g_hash_table_insert (gdm_languages_map,
+ g_strdup (ccode),
+ g_strdup (lang_name));
+ }
+ if (ccode_longB != NULL) {
+ g_hash_table_insert (gdm_languages_map,
+ g_strdup (ccode_longB),
+ g_strdup (lang_name));
+ }
+ if (ccode_longT != NULL) {
+ g_hash_table_insert (gdm_languages_map,
+ g_strdup (ccode_longT),
+ g_strdup (lang_name));
+ }
+}
+
+static void
+territories_parse_start_tag (GMarkupParseContext *ctx,
+ const char *element_name,
+ const char **attr_names,
+ const char **attr_values,
+ gpointer user_data,
+ GError **error)
+{
+ const char *acode_2;
+ const char *acode_3;
+ const char *ncode;
+ const char *territory_common_name;
+ const char *territory_name;
+
+ if (! g_str_equal (element_name, "iso_3166_entry") || attr_names == NULL || attr_values == NULL) {
+ return;
+ }
+
+ acode_2 = NULL;
+ acode_3 = NULL;
+ ncode = NULL;
+ territory_common_name = NULL;
+ territory_name = NULL;
+
+ while (*attr_names && *attr_values) {
+ if (g_str_equal (*attr_names, "alpha_2_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 2) {
+ return;
+ }
+ acode_2 = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "alpha_3_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 3) {
+ return;
+ }
+ acode_3 = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "numeric_code")) {
+ /* skip if empty */
+ if (**attr_values) {
+ if (strlen (*attr_values) != 3) {
+ return;
+ }
+ ncode = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "common_name")) {
+ /* skip if empty */
+ if (**attr_values) {
+ territory_common_name = *attr_values;
+ }
+ } else if (g_str_equal (*attr_names, "name")) {
+ territory_name = *attr_values;
+ }
+
+ ++attr_names;
+ ++attr_values;
+ }
+
+ if (territory_common_name != NULL) {
+ territory_name = territory_common_name;
+ }
+
+ if (territory_name == NULL) {
+ return;
+ }
+
+ if (acode_2 != NULL) {
+ g_hash_table_insert (gdm_territories_map,
+ g_strdup (acode_2),
+ g_strdup (territory_name));
+ }
+ if (acode_3 != NULL) {
+ g_hash_table_insert (gdm_territories_map,
+ g_strdup (acode_3),
+ g_strdup (territory_name));
+ }
+ if (ncode != NULL) {
+ g_hash_table_insert (gdm_territories_map,
+ g_strdup (ncode),
+ g_strdup (territory_name));
+ }
+}
+
+static void
+languages_init (void)
+{
+ GError *error;
+ gboolean res;
+ char *buf;
+ gsize buf_len;
+
+ bindtextdomain ("iso_639", ISO_CODES_LOCALESDIR);
+ bind_textdomain_codeset ("iso_639", "UTF-8");
+
+ gdm_languages_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ error = NULL;
+ res = g_file_get_contents (ISO_CODES_DATADIR "/iso_639.xml",
+ &buf,
+ &buf_len,
+ &error);
+ if (res) {
+ GMarkupParseContext *ctx;
+ GMarkupParser parser = { languages_parse_start_tag, NULL, NULL, NULL, NULL };
+
+ ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
+
+ error = NULL;
+ res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
+
+ if (! res) {
+ g_warning ("Failed to parse '%s': %s\n",
+ ISO_CODES_DATADIR "/iso_639.xml",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_markup_parse_context_free (ctx);
+ g_free (buf);
+ } else {
+ g_warning ("Failed to load '%s': %s\n",
+ ISO_CODES_DATADIR "/iso_639.xml",
+ error->message);
+ g_error_free (error);
+ }
+}
+
+static void
+territories_init (void)
+{
+ GError *error;
+ gboolean res;
+ char *buf;
+ gsize buf_len;
+
+ bindtextdomain ("iso_3166", ISO_CODES_LOCALESDIR);
+ bind_textdomain_codeset ("iso_3166", "UTF-8");
+
+ gdm_territories_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+ error = NULL;
+ res = g_file_get_contents (ISO_CODES_DATADIR "/iso_3166.xml",
+ &buf,
+ &buf_len,
+ &error);
+ if (res) {
+ GMarkupParseContext *ctx;
+ GMarkupParser parser = { territories_parse_start_tag, NULL, NULL, NULL, NULL };
+
+ ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
+
+ error = NULL;
+ res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
+
+ if (! res) {
+ g_warning ("Failed to parse '%s': %s\n",
+ ISO_CODES_DATADIR "/iso_3166.xml",
+ error->message);
+ g_error_free (error);
+ }
+
+ g_markup_parse_context_free (ctx);
+ g_free (buf);
+ } else {
+ g_warning ("Failed to load '%s': %s\n",
+ ISO_CODES_DATADIR "/iso_3166.xml",
+ error->message);
+ g_error_free (error);
+ }
+}
+
+char *
+gdm_get_language_from_name (const char *name,
+ const char *locale)
+{
+ char *full_language;
+ char *language_code;
+ char *territory_code;
+ const char *language;
+ const char *territory;
+
+ if (gdm_languages_map == NULL) {
+ languages_init ();
+ }
+
+ if (gdm_territories_map == NULL) {
+ territories_init ();
+ }
+
+ language_code = NULL;
+ territory_code = NULL;
+ full_language = NULL;
+
+ gdm_parse_language_name (name, &language_code, &territory_code,
+ NULL, NULL);
+
+ if (language_code == NULL) {
+ goto out;
+ }
+
+ language = get_translated_language (language_code, locale);
+
+ if (territory_code != NULL) {
+ territory = get_translated_territory (territory_code, locale);
+ } else {
+ territory = NULL;
+ }
+
+ if (territory != NULL) {
+ full_language = g_strdup_printf ("%s (%s)",
+ language ? language : "",
+ territory ? territory : "");
+ } else {
+ full_language = g_strdup (language);
+ }
+
+out:
+ g_free (language_code);
+ g_free (territory_code);
+
+ return full_language;
+}
+
+char **
+gdm_get_all_language_names (void)
+{
+ GHashTableIter iter;
+ gpointer key, value;
+ GPtrArray *array;
+
+ if (gdm_available_locales_map == NULL) {
+ collect_locales ();
+ }
+
+ array = g_ptr_array_new ();
+ g_hash_table_iter_init (&iter, gdm_available_locales_map);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ GdmLocale *locale;
+
+ locale = (GdmLocale *) value;
+
+ g_ptr_array_add (array, g_strdup (locale->name));
+ }
+ g_ptr_array_add (array, NULL);
+
+ return (char **) g_ptr_array_free (array, FALSE);
+}
diff --git a/panels/user-accounts/gdm-languages.h b/panels/user-accounts/gdm-languages.h
new file mode 100644
index 000000000..5b4560fda
--- /dev/null
+++ b/panels/user-accounts/gdm-languages.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2008 Red Hat, Inc.
+ * Copyright 2007 William Jon McCann
+ *
+ * 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.
+ *
+ * Written by: Ray Strode
+ * William Jon McCann
+ */
+
+#ifndef __GDM_LANGUAGES_H
+#define __GDM_LANGUAGES_H
+
+G_BEGIN_DECLS
+
+char * gdm_get_language_from_name (const char *name,
+ const char *locale);
+char ** gdm_get_all_language_names (void);
+gboolean gdm_parse_language_name (const char *name,
+ char **language_codep,
+ char **territory_codep,
+ char **codesetp,
+ char **modifierp);
+char * gdm_normalize_language_name (const char *name);
+
+G_END_DECLS
+
+#endif /* __GDM_LANGUAGE_CHOOSER_WIDGET_H */
diff --git a/panels/user-accounts/locarchive.h b/panels/user-accounts/locarchive.h
new file mode 100644
index 000000000..f933f4d91
--- /dev/null
+++ b/panels/user-accounts/locarchive.h
@@ -0,0 +1,97 @@
+/* Definitions for locale archive handling.
+ Copyright (C) 2002 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, write to the Free
+ Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+ 02111-1307 USA. */
+
+#ifndef _LOCARCHIVE_H
+#define _LOCARCHIVE_H 1
+
+#include
+
+#define AR_MAGIC 0xde020109
+
+struct locarhead
+{
+ uint32_t magic;
+ /* Serial number. */
+ uint32_t serial;
+ /* Name hash table. */
+ uint32_t namehash_offset;
+ uint32_t namehash_used;
+ uint32_t namehash_size;
+ /* String table. */
+ uint32_t string_offset;
+ uint32_t string_used;
+ uint32_t string_size;
+ /* Table with locale records. */
+ uint32_t locrectab_offset;
+ uint32_t locrectab_used;
+ uint32_t locrectab_size;
+ /* MD5 sum hash table. */
+ uint32_t sumhash_offset;
+ uint32_t sumhash_used;
+ uint32_t sumhash_size;
+};
+
+
+struct namehashent
+{
+ /* Hash value of the name. */
+ uint32_t hashval;
+ /* Offset of the name in the string table. */
+ uint32_t name_offset;
+ /* Offset of the locale record. */
+ uint32_t locrec_offset;
+};
+
+
+struct sumhashent
+{
+ /* MD5 sum. */
+ char sum[16];
+ /* Offset of the file in the archive. */
+ uint32_t file_offset;
+};
+
+struct locrecent
+{
+ uint32_t refs; /* # of namehashent records that point here */
+ struct
+ {
+ uint32_t offset;
+ uint32_t len;
+ } record[__LC_LAST];
+};
+
+
+struct locarhandle
+{
+ int fd;
+ void *addr;
+ size_t len;
+};
+
+
+/* In memory data for the locales with their checksums. */
+typedef struct locale_category_data
+{
+ off_t size;
+ void *addr;
+ char sum[16];
+} locale_data_t[__LC_LAST];
+
+#endif /* locarchive.h */
diff --git a/panels/user-accounts/run-passwd.c b/panels/user-accounts/run-passwd.c
new file mode 100644
index 000000000..6f7cfabc4
--- /dev/null
+++ b/panels/user-accounts/run-passwd.c
@@ -0,0 +1,772 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* run-passwd.c: this file is part of users-admin, a gnome-system-tools frontend
+ * for user administration.
+ *
+ * Copyright (C) 2002 Diego Gonzalez
+ * Copyright (C) 2006 Johannes H. Jensen
+ * Copyright (C) 2010 Milan Bouchet-Valat
+ *
+ * Written by: Diego Gonzalez
+ * Modified by: Johannes H. Jensen ,
+ * Milan Bouchet-Valat .
+ *
+ * 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, 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.
+ *
+ * Most of this code originally comes from gnome-about-me-password.c,
+ * from gnome-control-center.
+ */
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#if __sun
+#include
+#include
+#endif
+
+#include "run-passwd.h"
+
+/* Passwd states */
+typedef enum {
+ PASSWD_STATE_NONE, /* Passwd is not asking for anything */
+ PASSWD_STATE_AUTH, /* Passwd is asking for our current password */
+ PASSWD_STATE_NEW, /* Passwd is asking for our new password */
+ PASSWD_STATE_RETYPE, /* Passwd is asking for our retyped new password */
+ PASSWD_STATE_DONE, /* Passwd succeeded but has not yet exited */
+ PASSWD_STATE_ERR /* Passwd reported an error but has not yet exited */
+} PasswdState;
+
+struct PasswdHandler {
+ const char *current_password;
+ const char *new_password;
+
+ /* Communication with the passwd program */
+ GPid backend_pid;
+
+ GIOChannel *backend_stdin;
+ GIOChannel *backend_stdout;
+
+ GQueue *backend_stdin_queue; /* Write queue to backend_stdin */
+
+ /* GMainLoop IDs */
+ guint backend_child_watch_id; /* g_child_watch_add (PID) */
+ guint backend_stdout_watch_id; /* g_io_add_watch (stdout) */
+
+ /* State of the passwd program */
+ PasswdState backend_state;
+ gboolean changing_password;
+
+ PasswdCallback auth_cb;
+ gpointer auth_cb_data;
+
+ PasswdCallback chpasswd_cb;
+ gpointer chpasswd_cb_data;
+};
+
+/* Buffer size for backend output */
+#define BUFSIZE 64
+
+
+static GQuark
+passwd_error_quark (void)
+{
+ static GQuark q = 0;
+
+ if (q == 0) {
+ q = g_quark_from_static_string("passwd_error");
+ }
+
+ return q;
+}
+
+/* Error handling */
+#define PASSWD_ERROR (passwd_error_quark ())
+
+
+static void
+stop_passwd (PasswdHandler *passwd_handler);
+
+static void
+free_passwd_resources (PasswdHandler *passwd_handler);
+
+static gboolean
+io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler);
+
+
+/*
+ * Spawning and closing of backend {{
+ */
+
+/* Child watcher */
+static void
+child_watch_cb (GPid pid, gint status, PasswdHandler *passwd_handler)
+{
+ if (WIFEXITED (status)) {
+ if (WEXITSTATUS (status) >= 255) {
+ g_warning ("Child exited unexpectedly");
+ }
+ if (WEXITSTATUS (status) == 0) {
+ if (passwd_handler->backend_state == PASSWD_STATE_RETYPE) {
+ passwd_handler->backend_state = PASSWD_STATE_DONE;
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ NULL,
+ passwd_handler->auth_cb_data);
+ }
+ }
+ }
+
+ free_passwd_resources (passwd_handler);
+}
+
+static void
+ignore_sigpipe (gpointer data)
+{
+ signal (SIGPIPE, SIG_IGN);
+}
+
+/* Spawn passwd backend
+ * Returns: TRUE on success, FALSE otherwise and sets error appropriately */
+static gboolean
+spawn_passwd (PasswdHandler *passwd_handler, GError **error)
+{
+ gchar *argv[2];
+ gchar *envp[1];
+ gint my_stdin, my_stdout, my_stderr;
+
+ argv[0] = "/usr/bin/passwd"; /* Is it safe to rely on a hard-coded path? */
+ argv[1] = NULL;
+
+ envp[0] = NULL; /* If we pass an empty array as the environment,
+ * will the childs environment be empty, and the
+ * locales set to the C default? From the manual:
+ * "If envp is NULL, the child inherits its
+ * parent'senvironment."
+ * If I'm wrong here, we somehow have to set
+ * the locales here.
+ */
+
+ if (!g_spawn_async_with_pipes (NULL, /* Working directory */
+ argv, /* Argument vector */
+ envp, /* Environment */
+ G_SPAWN_DO_NOT_REAP_CHILD, /* Flags */
+ ignore_sigpipe, /* Child setup */
+ NULL, /* Data to child setup */
+ &passwd_handler->backend_pid, /* PID */
+ &my_stdin, /* Stdin */
+ &my_stdout, /* Stdout */
+ &my_stderr, /* Stderr */
+ error)) { /* GError */
+
+ /* An error occured */
+ free_passwd_resources (passwd_handler);
+
+ return FALSE;
+ }
+
+ /* 2>&1 */
+ if (dup2 (my_stderr, my_stdout) == -1) {
+ /* Failed! */
+ g_set_error_literal (error,
+ PASSWD_ERROR,
+ PASSWD_ERROR_BACKEND,
+ strerror (errno));
+
+ /* Clean up */
+ stop_passwd (passwd_handler);
+
+ return FALSE;
+ }
+
+ /* Open IO Channels */
+ passwd_handler->backend_stdin = g_io_channel_unix_new (my_stdin);
+ passwd_handler->backend_stdout = g_io_channel_unix_new (my_stdout);
+
+ /* Set raw encoding */
+ /* Set nonblocking mode */
+ if (g_io_channel_set_encoding (passwd_handler->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
+ g_io_channel_set_encoding (passwd_handler->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
+ g_io_channel_set_flags (passwd_handler->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
+ g_io_channel_set_flags (passwd_handler->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {
+
+ /* Clean up */
+ stop_passwd (passwd_handler);
+ return FALSE;
+ }
+
+ /* Turn off buffering */
+ g_io_channel_set_buffered (passwd_handler->backend_stdin, FALSE);
+ g_io_channel_set_buffered (passwd_handler->backend_stdout, FALSE);
+
+ /* Add IO Channel watcher */
+ passwd_handler->backend_stdout_watch_id = g_io_add_watch (passwd_handler->backend_stdout,
+ G_IO_IN | G_IO_PRI,
+ (GIOFunc) io_watch_stdout, passwd_handler);
+
+ /* Add child watcher */
+ passwd_handler->backend_child_watch_id = g_child_watch_add (passwd_handler->backend_pid, (GChildWatchFunc) child_watch_cb, passwd_handler);
+
+ /* Success! */
+
+ return TRUE;
+}
+
+/* Stop passwd backend */
+static void
+stop_passwd (PasswdHandler *passwd_handler)
+{
+ /* This is the standard way of returning from the dialog with passwd.
+ * If we return this way we can safely kill passwd as it has completed
+ * its task.
+ */
+
+ if (passwd_handler->backend_pid != -1) {
+ kill (passwd_handler->backend_pid, 9);
+ }
+
+ /* We must run free_passwd_resources here and not let our child
+ * watcher do it, since it will access invalid memory after the
+ * dialog has been closed and cleaned up.
+ *
+ * If we had more than a single thread we'd need to remove
+ * the child watch before trying to kill the child.
+ */
+ free_passwd_resources (passwd_handler);
+}
+
+/* Clean up passwd resources */
+static void
+free_passwd_resources (PasswdHandler *passwd_handler)
+{
+ GError *error = NULL;
+
+ /* Remove the child watcher */
+ if (passwd_handler->backend_child_watch_id != 0) {
+
+ g_source_remove (passwd_handler->backend_child_watch_id);
+
+ passwd_handler->backend_child_watch_id = 0;
+ }
+
+
+ /* Close IO channels (internal file descriptors are automatically closed) */
+ if (passwd_handler->backend_stdin != NULL) {
+
+ if (g_io_channel_shutdown (passwd_handler->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) {
+ g_warning ("Could not shutdown backend_stdin IO channel: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ g_io_channel_unref (passwd_handler->backend_stdin);
+ passwd_handler->backend_stdin = NULL;
+ }
+
+ if (passwd_handler->backend_stdout != NULL) {
+
+ if (g_io_channel_shutdown (passwd_handler->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) {
+ g_warning ("Could not shutdown backend_stdout IO channel: %s", error->message);
+ g_error_free (error);
+ error = NULL;
+ }
+
+ g_io_channel_unref (passwd_handler->backend_stdout);
+
+ passwd_handler->backend_stdout = NULL;
+ }
+
+ /* Remove IO watcher */
+ if (passwd_handler->backend_stdout_watch_id != 0) {
+
+ g_source_remove (passwd_handler->backend_stdout_watch_id);
+
+ passwd_handler->backend_stdout_watch_id = 0;
+ }
+
+ /* Close PID */
+ if (passwd_handler->backend_pid != -1) {
+
+ g_spawn_close_pid (passwd_handler->backend_pid);
+
+ passwd_handler->backend_pid = -1;
+ }
+
+ /* Clear backend state */
+ passwd_handler->backend_state = PASSWD_STATE_NONE;
+}
+
+/*
+ * }} Spawning and closing of backend
+ */
+
+/*
+ * Backend communication code {{
+ */
+
+/* Write the first element of queue through channel */
+static void
+io_queue_pop (GQueue *queue, GIOChannel *channel)
+{
+ gchar *buf;
+ gsize bytes_written;
+ GError *error = NULL;
+
+ buf = g_queue_pop_head (queue);
+
+ if (buf != NULL) {
+
+ if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) {
+ g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message);
+ g_error_free (error);
+ }
+
+ /* Ensure passwords are cleared from memory */
+ memset (buf, 0, strlen (buf));
+ g_free (buf);
+ }
+}
+
+/* Goes through the argument list, checking if one of them occurs in str
+ * Returns: TRUE as soon as an element is found to match, FALSE otherwise */
+static gboolean
+is_string_complete (gchar *str, ...)
+{
+ va_list ap;
+ gchar *arg;
+
+ if (strlen (str) == 0) {
+ return FALSE;
+ }
+
+ va_start (ap, str);
+
+ while ((arg = va_arg (ap, char *)) != NULL) {
+ if (strstr (str, arg) != NULL) {
+ va_end (ap);
+ return TRUE;
+ }
+ }
+
+ va_end (ap);
+
+ return FALSE;
+}
+
+/*
+ * IO watcher for stdout, called whenever there is data to read from the backend.
+ * This is where most of the actual IO handling happens.
+ */
+static gboolean
+io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler)
+{
+ static GString *str = NULL; /* Persistent buffer */
+
+ gchar buf[BUFSIZE]; /* Temporary buffer */
+ gsize bytes_read;
+ GError *gio_error = NULL; /* Error returned by functions */
+ GError *error = NULL; /* Error sent to callbacks */
+
+ gboolean reinit = FALSE;
+
+ /* Initialize buffer */
+ if (str == NULL) {
+ str = g_string_new ("");
+ }
+
+ if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &gio_error)
+ != G_IO_STATUS_NORMAL) {
+ g_warning ("IO Channel read error: %s", gio_error->message);
+ g_error_free (gio_error);
+
+ return TRUE;
+ }
+
+ str = g_string_append_len (str, buf, bytes_read);
+
+ /* In which state is the backend? */
+ switch (passwd_handler->backend_state) {
+ case PASSWD_STATE_AUTH:
+ /* Passwd is asking for our current password */
+
+ if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
+
+ if (strstr (str->str, "assword: ") != NULL) {
+ /* Authentication successful */
+
+ passwd_handler->backend_state = PASSWD_STATE_NEW;
+
+ /* Trigger callback to update authentication status */
+ if (passwd_handler->auth_cb)
+ passwd_handler->auth_cb (passwd_handler,
+ NULL,
+ passwd_handler->auth_cb_data);
+
+ } else {
+ /* Authentication failed */
+
+ error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
+ _("Authentication failed"));
+
+ passwd_handler->changing_password = FALSE;
+
+ /* This error can happen both while authenticating or while changing password:
+ * if chpasswd_cb is set, this means we're already changing password */
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ error,
+ passwd_handler->auth_cb_data);
+ else if (passwd_handler->auth_cb)
+ passwd_handler->auth_cb (passwd_handler,
+ error,
+ passwd_handler->auth_cb_data);
+
+ g_error_free (error);
+ }
+
+ reinit = TRUE;
+ }
+ break;
+ case PASSWD_STATE_NEW:
+ /* Passwd is asking for our new password */
+
+ if (is_string_complete (str->str, "assword: ", NULL)) {
+ /* Advance to next state */
+ passwd_handler->backend_state = PASSWD_STATE_RETYPE;
+
+ /* Pop retyped password from queue and into IO channel */
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+ reinit = TRUE;
+ }
+ break;
+ case PASSWD_STATE_RETYPE:
+ /* Passwd is asking for our retyped new password */
+
+ if (is_string_complete (str->str,
+ "successfully",
+ "short",
+ "longer",
+ "palindrome",
+ "dictionary",
+ "simple",
+ "simplistic",
+ "similar",
+ "case",
+ "different",
+ "wrapped",
+ "recovered",
+ "recent",
+ "unchanged",
+ "match",
+ "1 numeric or special",
+ "failure",
+ "DIFFERENT",
+ "BAD PASSWORD",
+ NULL)) {
+
+ if (strstr (str->str, "successfully") != NULL) {
+ /* Hooray! */
+
+ passwd_handler->backend_state = PASSWD_STATE_DONE;
+ /* Trigger callback to update status */
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ NULL,
+ passwd_handler->chpasswd_cb_data);
+ }
+ else {
+ /* Ohnoes! */
+
+ if (strstr (str->str, "recovered") != NULL) {
+ /* What does this indicate?
+ * "Authentication information cannot be recovered?" from libpam? */
+ error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
+ str->str);
+ } else if (strstr (str->str, "short") != NULL ||
+ strstr (str->str, "longer") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password is too short"));
+ } else if (strstr (str->str, "palindrome") != NULL ||
+ strstr (str->str, "simple") != NULL ||
+ strstr (str->str, "simplistic") != NULL ||
+ strstr (str->str, "dictionary") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password is too simple"));
+ } else if (strstr (str->str, "similar") != NULL ||
+ strstr (str->str, "different") != NULL ||
+ strstr (str->str, "case") != NULL ||
+ strstr (str->str, "wrapped") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The old and new passwords are too similar"));
+ } else if (strstr (str->str, "recent") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password has already been used recently."));
+ } else if (strstr (str->str, "1 numeric or special") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password must contain numeric or special characters"));
+ } else if (strstr (str->str, "unchanged") != NULL ||
+ strstr (str->str, "match") != NULL) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The old and new passwords are the same"));
+ } else if (strstr (str->str, "failure") != NULL) {
+ /* Authentication failure */
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
+ _("Your password has been changed since you initially authenticated!"));
+ }
+ else if (strstr (str->str, "DIFFERENT")) {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
+ _("The new password does not contain enough different characters"));
+ }
+ else {
+ error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
+ _("Unknown error"));
+ }
+
+ /* At this point, passwd might have exited, in which case
+ * child_watch_cb should clean up for us and remove this watcher.
+ * On some error conditions though, passwd just re-prompts us
+ * for our new password. */
+ passwd_handler->backend_state = PASSWD_STATE_ERR;
+
+ passwd_handler->changing_password = FALSE;
+
+ /* Trigger callback to update status */
+ if (passwd_handler->chpasswd_cb)
+ passwd_handler->chpasswd_cb (passwd_handler,
+ error,
+ passwd_handler->chpasswd_cb_data);
+
+ g_error_free (error);
+
+ }
+
+ reinit = TRUE;
+
+ /* child_watch_cb should clean up for us now */
+ }
+ break;
+ case PASSWD_STATE_NONE:
+ /* Passwd is not asking for anything yet */
+ if (is_string_complete (str->str, "assword: ", NULL)) {
+
+ /* If the user does not have a password set,
+ * passwd will immediately ask for the new password,
+ * so skip the AUTH phase */
+ if (is_string_complete (str->str, "new", "New", NULL)) {
+ gchar *pw;
+
+ passwd_handler->backend_state = PASSWD_STATE_NEW;
+
+ /* since passwd didn't ask for our old password
+ * in this case, simply remove it from the queue */
+ pw = g_queue_pop_head (passwd_handler->backend_stdin_queue);
+ g_free (pw);
+
+ /* Pop the IO queue, i.e. send new password */
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+ } else {
+
+ passwd_handler->backend_state = PASSWD_STATE_AUTH;
+
+ /* Pop the IO queue, i.e. send current password */
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+ }
+
+ reinit = TRUE;
+ }
+ break;
+ default:
+ /* Passwd has returned an error */
+ reinit = TRUE;
+ break;
+ }
+
+ if (reinit) {
+ g_string_free (str, TRUE);
+ str = NULL;
+ }
+
+ /* Continue calling us */
+ return TRUE;
+}
+
+/*
+ * }} Backend communication code
+ */
+
+/* Adds the current password to the IO queue */
+static void
+authenticate (PasswdHandler *passwd_handler)
+{
+ gchar *s;
+
+ s = g_strdup_printf ("%s\n", passwd_handler->current_password);
+
+ g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
+}
+
+/* Adds the new password twice to the IO queue */
+static void
+update_password (PasswdHandler *passwd_handler)
+{
+ gchar *s;
+
+ s = g_strdup_printf ("%s\n", passwd_handler->new_password);
+
+ g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
+ /* We need to allocate new space because io_queue_pop() g_free()s
+ * every element of the queue after it's done */
+ g_queue_push_tail (passwd_handler->backend_stdin_queue, g_strdup (s));
+}
+
+
+PasswdHandler *
+passwd_init (void)
+{
+ PasswdHandler *passwd_handler;
+
+ passwd_handler = g_new0 (PasswdHandler, 1);
+
+ /* Initialize backend_pid. -1 means the backend is not running */
+ passwd_handler->backend_pid = -1;
+
+ /* Initialize IO Channels */
+ passwd_handler->backend_stdin = NULL;
+ passwd_handler->backend_stdout = NULL;
+
+ /* Initialize write queue */
+ passwd_handler->backend_stdin_queue = g_queue_new ();
+
+ /* Initialize watchers */
+ passwd_handler->backend_child_watch_id = 0;
+ passwd_handler->backend_stdout_watch_id = 0;
+
+ /* Initialize backend state */
+ passwd_handler->backend_state = PASSWD_STATE_NONE;
+ passwd_handler->changing_password = FALSE;
+
+ return passwd_handler;
+}
+
+void
+passwd_destroy (PasswdHandler *passwd_handler)
+{
+ g_queue_free (passwd_handler->backend_stdin_queue);
+ stop_passwd (passwd_handler);
+ g_free (passwd_handler);
+}
+
+void
+passwd_authenticate (PasswdHandler *passwd_handler,
+ const char *current_password,
+ PasswdCallback cb,
+ const gpointer user_data)
+{
+ GError *error = NULL;
+
+ /* Don't stop if we've already started chaging password */
+ if (passwd_handler->changing_password)
+ return;
+
+ /* Clear data from possible previous attempts to change password */
+ passwd_handler->new_password = NULL;
+ passwd_handler->chpasswd_cb = NULL;
+ passwd_handler->chpasswd_cb_data = NULL;
+ g_queue_foreach (passwd_handler->backend_stdin_queue, (GFunc) g_free, NULL);
+ g_queue_clear (passwd_handler->backend_stdin_queue);
+
+ passwd_handler->current_password = current_password;
+ passwd_handler->auth_cb = cb;
+ passwd_handler->auth_cb_data = user_data;
+
+ /* Spawn backend */
+ stop_passwd (passwd_handler);
+
+ if (!spawn_passwd (passwd_handler, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ return;
+ }
+
+ authenticate (passwd_handler);
+
+ /* Our IO watcher should now handle the rest */
+}
+
+gboolean
+passwd_change_password (PasswdHandler *passwd_handler,
+ const char *new_password,
+ PasswdCallback cb,
+ const gpointer user_data)
+{
+ GError *error = NULL;
+
+ passwd_handler->changing_password = TRUE;
+
+ passwd_handler->new_password = new_password;
+ passwd_handler->chpasswd_cb = cb;
+ passwd_handler->chpasswd_cb_data = user_data;
+
+ /* Stop passwd if an error occured and it is still running */
+ if (passwd_handler->backend_state == PASSWD_STATE_ERR) {
+
+ /* Stop passwd, free resources */
+ stop_passwd (passwd_handler);
+ }
+
+ /* Check that the backend is still running, or that an error
+ * has occured but it has not yet exited */
+ if (passwd_handler->backend_pid == -1) {
+ /* If it is not, re-run authentication */
+
+ /* Spawn backend */
+ stop_passwd (passwd_handler);
+
+ if (!spawn_passwd (passwd_handler, &error)) {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ return FALSE;
+ }
+
+ /* Add current and new passwords to queue */
+ authenticate (passwd_handler);
+ update_password (passwd_handler);
+ } else {
+ /* Only add new passwords to queue */
+ update_password (passwd_handler);
+ }
+
+ /* Pop new password through the backend.
+ * If user has no password, popping the queue would output current
+ * password, while 'passwd' is waiting for the new one. So wait for
+ * io_watch_stdout() to remove current password from the queue,
+ * and output the new one for us.
+ */
+ if (passwd_handler->current_password)
+ io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
+
+ /* Our IO watcher should now handle the rest */
+
+ return TRUE;
+}
diff --git a/panels/user-accounts/run-passwd.h b/panels/user-accounts/run-passwd.h
new file mode 100644
index 000000000..2b07185a3
--- /dev/null
+++ b/panels/user-accounts/run-passwd.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* run-passwd.h: this file is part of users-admin, a gnome-system-tools frontend
+ * for user administration.
+ *
+ * Copyright (C) 2010 Milan Bouchet-Valat
+ *
+ * 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.
+ *
+ * Authors: Milan Bouchet-Valat
+ */
+
+#ifndef _RUN_PASSWD_H
+#define _RUN_PASSWD_H
+
+struct PasswdHandler;
+
+typedef struct PasswdHandler PasswdHandler;
+
+typedef void (*PasswdCallback) (PasswdHandler *passwd_handler, GError *error, const gpointer user_data);
+
+/* Error codes */
+typedef enum {
+ PASSWD_ERROR_REJECTED, /* New password is not secure enough */
+ PASSWD_ERROR_AUTH_FAILED, /* Wrong old password, or PAM failure */
+ PASSWD_ERROR_REAUTH_FAILED, /* Password has changed since first authentication */
+ PASSWD_ERROR_BACKEND, /* Backend error */
+ PASSWD_ERROR_UNKNOWN /* General error */
+} PasswdError;
+
+
+PasswdHandler *passwd_init (void);
+
+void passwd_destroy (PasswdHandler *passwd_handler);
+
+void passwd_authenticate (PasswdHandler *passwd_handler,
+ const char *current_password,
+ PasswdCallback cb,
+ gpointer user_data);
+
+gboolean passwd_change_password (PasswdHandler *passwd_handler,
+ const char *new_password,
+ PasswdCallback cb,
+ const gpointer user_data);
+
+#endif /* _RUN_PASSWD_H */
+
diff --git a/panels/user-accounts/um-account-dialog.c b/panels/user-accounts/um-account-dialog.c
new file mode 100644
index 000000000..93e10b584
--- /dev/null
+++ b/panels/user-accounts/um-account-dialog.c
@@ -0,0 +1,493 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "um-account-dialog.h"
+#include "um-user-manager.h"
+#include "um-utils.h"
+
+#define MAXNAMELEN (UT_NAMESIZE - 1)
+
+struct _UmAccountDialog {
+ GtkWidget *dialog;
+ GtkWidget *username_combo;
+ GtkWidget *name_entry;
+ GtkWidget *account_type_combo;
+ GtkWidget *ok_button;
+
+ gboolean valid_name;
+ gboolean valid_username;
+
+ UserCreatedCallback user_created_callback;
+ gpointer user_created_data;
+};
+
+static void
+cancel_account_dialog (GtkButton *button,
+ UmAccountDialog *um)
+{
+ gtk_widget_hide (um->dialog);
+}
+
+static void
+create_user_done (UmUserManager *manager,
+ GAsyncResult *res,
+ UmAccountDialog *um)
+{
+ UmUser *user;
+ GError *error;
+
+ error = NULL;
+ if (!um_user_manager_create_user_finish (manager, res, &user, &error)) {
+
+ if (!g_error_matches (error, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_PERMISSION_DENIED)) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (gtk_window_get_transient_for (GTK_WINDOW (um->dialog)),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Failed to create user"));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+ g_error_free (error);
+ }
+ else {
+ um->user_created_callback (user, um->user_created_data);
+ }
+}
+
+static void
+accept_account_dialog (GtkButton *button,
+ UmAccountDialog *um)
+{
+ UmUserManager *manager;
+ const gchar *username;
+ const gchar *name;
+ gint account_type;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ name = gtk_entry_get_text (GTK_ENTRY (um->name_entry));
+ username = gtk_combo_box_get_active_text (GTK_COMBO_BOX (um->username_combo));
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->account_type_combo));
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (um->account_type_combo), &iter);
+ gtk_tree_model_get (model, &iter, 1, &account_type, -1);
+
+ manager = um_user_manager_ref_default ();
+ um_user_manager_create_user (manager,
+ username,
+ name,
+ account_type,
+ (GAsyncReadyCallback)create_user_done,
+ um,
+ NULL);
+ g_object_unref (manager);
+
+ gtk_widget_hide (um->dialog);
+}
+
+static gboolean
+is_username_used (const gchar *username)
+{
+ struct passwd *pwent;
+
+ pwent = getpwnam (username);
+
+ return pwent != NULL;
+}
+
+static void
+username_changed (GtkComboBox *combo,
+ UmAccountDialog *um)
+{
+ gboolean in_use;
+ gboolean empty;
+ gboolean valid;
+ gboolean toolong;
+ const gchar *username;
+ const gchar *c;
+ gchar *tip;
+ GtkWidget *entry;
+
+ username = gtk_combo_box_get_active_text (combo);
+
+ in_use = is_username_used (username);
+ empty = username[0] == 0;
+ toolong = strlen (username) > MAXNAMELEN;
+ valid = TRUE;
+
+ if (!in_use && !empty && !toolong) {
+ /* First char must be a letter, and it must only composed
+ * of ASCII letters, digits, and a '.', '-', '_'
+ */
+ for (c = username; *c; c++) {
+ if (! ((*c >= 'a' && *c <= 'z') ||
+ (*c >= 'A' && *c <= 'Z') ||
+ (*c >= '0' && *c <= '9') ||
+ (*c == '_') || (*c == '.') ||
+ (*c == '-' && c != username)))
+ valid = FALSE;
+ }
+ }
+
+ um->valid_username = !empty && !in_use && !toolong && valid;
+ gtk_widget_set_sensitive (um->ok_button, um->valid_name && um->valid_username);
+
+ entry = gtk_bin_get_child (GTK_BIN (combo));
+
+ if (!empty && (in_use || toolong || !valid)) {
+ if (in_use) {
+ tip = g_strdup_printf (_("A user with the username '%s' already exists"),
+ username);
+ }
+ else if (toolong) {
+ tip = g_strdup_printf (_("The username is too long"));
+ }
+ else if (username[0] == '-') {
+ tip = g_strdup (_("The username cannot start with a '-'"));
+ }
+ else {
+ tip = g_strdup (_("The username must consist of:\n"
+ " \xe2\x9e\xa3 letters from the English alphabet\n"
+ " \xe2\x9e\xa3 digits\n"
+ " \xe2\x9e\xa3 any of the characters '.', '-' and '_'"));
+ }
+
+ set_entry_validation_error (GTK_ENTRY (entry), tip);
+
+ g_free (tip);
+ }
+ else {
+ clear_entry_validation_error (GTK_ENTRY (entry));
+ }
+}
+
+static void
+name_changed (GtkEntry *name_entry,
+ GParamSpec *pspec,
+ UmAccountDialog *um)
+{
+ GtkWidget *entry;
+ GtkTreeModel *model;
+ gboolean in_use;
+ const char *name;
+ char *lc_name, *ascii_name, *stripped_name;
+ char **words1;
+ char **words2 = NULL;
+ char **w1, **w2;
+ char *c;
+ char *unicode_fallback = "?";
+ GString *first_word, *last_word;
+ GString *item1, *item2, *item3, *item4;
+ int len;
+ int nwords1, nwords2, i;
+ GHashTable *items;
+
+ entry = gtk_bin_get_child (GTK_BIN (um->username_combo));
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->username_combo));
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+
+ name = gtk_entry_get_text (GTK_ENTRY (name_entry));
+
+ um->valid_name = (strlen (name) > 0);
+ gtk_widget_set_sensitive (um->ok_button, um->valid_name && um->valid_username);
+
+ if (!um->valid_name) {
+ gtk_entry_set_text (GTK_ENTRY (entry), "");
+ return;
+ }
+
+ ascii_name = g_convert_with_fallback (name, -1, "ASCII//TRANSLIT", "UTF-8",
+ unicode_fallback, NULL, NULL, NULL);
+
+ lc_name = g_ascii_strdown (ascii_name, -1);
+
+ /* remove all non ASCII alphanumeric chars from the name,
+ * apart from the few allowed symbols
+ */
+ stripped_name = g_strnfill (strlen (lc_name) + 1, '\0');
+ i = 0;
+ for (c = lc_name; *c; c++) {
+ if (!(g_ascii_isdigit (*c) || g_ascii_islower (*c) ||
+ *c == ' ' || *c == '-' || *c == '.' || *c == '_' ||
+ /* used to track invalid words, removed below */
+ *c == '?') )
+ continue;
+
+ stripped_name[i] = *c;
+ i++;
+ }
+
+ if (strlen (stripped_name) == 0) {
+ g_free (ascii_name);
+ g_free (lc_name);
+ g_free (stripped_name);
+ return;
+ }
+
+ /* we split name on spaces, and then on dashes, so that we can treat
+ * words linked with dashes the same way, i.e. both fully shown, or
+ * both abbreviated
+ */
+ words1 = g_strsplit_set (stripped_name, " ", -1);
+ len = g_strv_length (words1);
+
+ g_free (ascii_name);
+ g_free (lc_name);
+ g_free (stripped_name);
+
+ /* Concatenate the whole first word with the first letter of each
+ * word (item1), and the last word with the first letter of each
+ * word (item2). item3 and item4 are symmetrical respectively to
+ * item1 and item2.
+ *
+ * Constant 5 is the max reasonable number of words we may get when
+ * splitting on dashes, since we can't guess it at this point,
+ * and reallocating would be too bad.
+ */
+ item1 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
+ item3 = g_string_sized_new (strlen (words1[0]) + len - 1 + 5);
+
+ item2 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
+ item4 = g_string_sized_new (strlen (words1[len - 1]) + len - 1 + 5);
+
+ /* again, guess at the max size of names */
+ first_word = g_string_sized_new (20);
+ last_word = g_string_sized_new (20);
+
+ nwords1 = 0;
+ nwords2 = 0;
+ for (w1 = words1; *w1; w1++) {
+ if (strlen (*w1) == 0)
+ continue;
+
+ /* skip words with string '?', most likely resulting
+ * from failed transliteration to ASCII
+ */
+ if (strstr (*w1, unicode_fallback) != NULL)
+ continue;
+
+ nwords1++; /* count real words, excluding empty string */
+
+ words2 = g_strsplit_set (*w1, "-", -1);
+ /* reset last word if a new non-empty word has been found */
+ if (strlen (*words2) > 0)
+ last_word = g_string_set_size (last_word, 0);
+
+ for (w2 = words2; *w2; w2++) {
+ if (strlen (*w2) == 0)
+ continue;
+
+ nwords2++;
+
+ /* part of the first "toplevel" real word */
+ if (nwords1 == 1) {
+ item1 = g_string_append (item1, *w2);
+ first_word = g_string_append (first_word, *w2);
+ }
+ else {
+ item1 = g_string_append_unichar (item1,
+ g_utf8_get_char (*w2));
+ item3 = g_string_append_unichar (item3,
+ g_utf8_get_char (*w2));
+ }
+
+ /* not part of the last "toplevel" word */
+ if (w1 != words1 + len - 1) {
+ item2 = g_string_append_unichar (item2,
+ g_utf8_get_char (*w2));
+ item4 = g_string_append_unichar (item4,
+ g_utf8_get_char (*w2));
+ }
+
+ /* always save current word so that we have it if last one reveals empty */
+ last_word = g_string_append (last_word, *w2);
+ }
+
+ g_strfreev (words2);
+ }
+ item2 = g_string_append (item2, last_word->str);
+ item3 = g_string_append (item3, first_word->str);
+ item4 = g_string_prepend (item4, last_word->str);
+
+ items = g_hash_table_new (g_str_hash, g_str_equal);
+
+ in_use = is_username_used (item1->str);
+ if (nwords2 > 0 && !in_use && !g_ascii_isdigit (item1->str[0])) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), item1->str);
+ g_hash_table_insert (items, item1->str, item1->str);
+ }
+
+ /* if there's only one word, would be the same as item1 */
+ if (nwords2 > 1) {
+ /* add other items */
+ in_use = is_username_used (item2->str);
+ if (!in_use && !g_ascii_isdigit (item2->str[0]) &&
+ !g_hash_table_lookup (items, item2->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), item2->str);
+ g_hash_table_insert (items, item2->str, item2->str);
+ }
+
+ in_use = is_username_used (item3->str);
+ if (!in_use && !g_ascii_isdigit (item3->str[0]) &&
+ !g_hash_table_lookup (items, item3->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), item3->str);
+ g_hash_table_insert (items, item3->str, item3->str);
+ }
+
+ in_use = is_username_used (item4->str);
+ if (!in_use && !g_ascii_isdigit (item4->str[0]) &&
+ !g_hash_table_lookup (items, item4->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), item4->str);
+ g_hash_table_insert (items, item4->str, item4->str);
+ }
+
+ /* add the last word */
+ in_use = is_username_used (last_word->str);
+ if (!in_use && !g_ascii_isdigit (last_word->str[0]) &&
+ !g_hash_table_lookup (items, last_word->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), last_word->str);
+ g_hash_table_insert (items, last_word->str, last_word->str);
+ }
+
+ /* ...and the first one */
+ in_use = is_username_used (first_word->str);
+ if (!in_use && !g_ascii_isdigit (first_word->str[0]) &&
+ !g_hash_table_lookup (items, first_word->str)) {
+ gtk_combo_box_append_text (GTK_COMBO_BOX (um->username_combo), first_word->str);
+ g_hash_table_insert (items, first_word->str, first_word->str);
+ }
+ }
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->username_combo), 0);
+ g_hash_table_destroy (items);
+ g_strfreev (words1);
+ g_string_free (first_word, TRUE);
+ g_string_free (last_word, TRUE);
+ g_string_free (item1, TRUE);
+ g_string_free (item2, TRUE);
+ g_string_free (item3, TRUE);
+ g_string_free (item4, TRUE);
+}
+
+UmAccountDialog *
+um_account_dialog_new (void)
+{
+ GtkBuilder *builder;
+ GtkWidget *widget;
+ UmAccountDialog *um;
+ const gchar *filename;
+ GError *error = NULL;
+
+ builder = gtk_builder_new ();
+
+ filename = UIDIR "/account-dialog.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/account-dialog.ui";
+ if (!gtk_builder_add_from_file (builder, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ um = g_new0 (UmAccountDialog, 1);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+ g_signal_connect (widget, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+ um->dialog = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (cancel_account_dialog), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (accept_account_dialog), um);
+ gtk_widget_grab_default (widget);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "username-combo");
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (username_changed), um);
+ um->username_combo = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "name-entry");
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (name_changed), um);
+ um->name_entry = widget;
+
+ um->ok_button = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "account-type-combo");
+ um->account_type_combo = widget;
+
+ return um;
+}
+
+void
+um_account_dialog_free (UmAccountDialog *um)
+{
+ gtk_widget_destroy (um->dialog);
+ g_free (um);
+}
+
+void
+um_account_dialog_show (UmAccountDialog *um,
+ GtkWindow *parent,
+ UserCreatedCallback user_created_callback,
+ gpointer user_created_data)
+{
+ GtkTreeModel *model;
+
+ gtk_entry_set_text (GTK_ENTRY (um->name_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (um->username_combo))), "");
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->username_combo));
+ gtk_list_store_clear (GTK_LIST_STORE (model));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->account_type_combo), 0);
+
+ gtk_window_set_transient_for (GTK_WINDOW (um->dialog), parent);
+ gtk_window_present (GTK_WINDOW (um->dialog));
+ gtk_widget_grab_focus (um->name_entry);
+
+ um->valid_name = um->valid_username = TRUE;
+
+ um->user_created_callback = user_created_callback;
+ um->user_created_data = user_created_data;
+}
+
+
diff --git a/panels/user-accounts/um-account-dialog.h b/panels/user-accounts/um-account-dialog.h
new file mode 100644
index 000000000..e3b2ebcc6
--- /dev/null
+++ b/panels/user-accounts/um-account-dialog.h
@@ -0,0 +1,43 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef __UM_ACCOUNT_DIALOG_H__
+#define __UM_ACCOUNT_DIALOG_H__
+
+#include
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+typedef struct _UmAccountDialog UmAccountDialog;
+
+typedef void (*UserCreatedCallback) (UmUser *user, gpointer data);
+
+UmAccountDialog *um_account_dialog_new (void);
+void um_account_dialog_free (UmAccountDialog *dialog);
+void um_account_dialog_show (UmAccountDialog *dialog,
+ GtkWindow *parent,
+ UserCreatedCallback user_created,
+ gpointer data);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-account-type.c b/panels/user-accounts/um-account-type.c
new file mode 100644
index 000000000..55064a8be
--- /dev/null
+++ b/panels/user-accounts/um-account-type.c
@@ -0,0 +1,43 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+
+#include "um-account-type.h"
+
+const gchar *
+um_account_type_get_name (UmAccountType account_type)
+{
+ switch (account_type) {
+ case UM_ACCOUNT_TYPE_STANDARD:
+ return C_("Account type", "Standard");
+ case UM_ACCOUNT_TYPE_ADMINISTRATOR:
+ return C_("Account type", "Administrator");
+ case UM_ACCOUNT_TYPE_SUPERVISED:
+ return C_("Account type", "Supervised");
+ default:
+ g_assert_not_reached ();
+ }
+}
diff --git a/panels/user-accounts/um-account-type.h b/panels/user-accounts/um-account-type.h
new file mode 100644
index 000000000..1e42e00c9
--- /dev/null
+++ b/panels/user-accounts/um-account-type.h
@@ -0,0 +1,37 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef __UM_ACCOUNT_TYPE__
+#define __UM_ACCOUNT_TYPE__
+
+G_BEGIN_DECLS
+
+typedef enum {
+ UM_ACCOUNT_TYPE_STANDARD,
+ UM_ACCOUNT_TYPE_ADMINISTRATOR,
+ UM_ACCOUNT_TYPE_SUPERVISED
+} UmAccountType;
+
+const gchar *um_account_type_get_name (UmAccountType account_type);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-crop-area.c b/panels/user-accounts/um-crop-area.c
new file mode 100644
index 000000000..4b6bacbec
--- /dev/null
+++ b/panels/user-accounts/um-crop-area.c
@@ -0,0 +1,817 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+
+#include
+#include
+#include
+
+#include "um-crop-area.h"
+
+struct _UmCropAreaPrivate {
+ GdkPixbuf *browse_pixbuf;
+ GdkPixbuf *pixbuf;
+ GdkPixbuf *color_shifted;
+ gdouble scale;
+ GdkRectangle image;
+ GdkCursorType current_cursor;
+ GdkRectangle crop;
+ gint active_region;
+ gint last_press_x;
+ gint last_press_y;
+ gint base_width;
+ gint base_height;
+ gdouble aspect;
+};
+
+G_DEFINE_TYPE (UmCropArea, um_crop_area, GTK_TYPE_DRAWING_AREA);
+
+static inline guchar
+shift_color_byte (guchar b,
+ int shift)
+{
+ return CLAMP(b + shift, 0, 255);
+}
+
+static void
+shift_colors (GdkPixbuf *pixbuf,
+ gint red,
+ gint green,
+ gint blue,
+ gint alpha)
+{
+ gint x, y, offset, y_offset, rowstride, width, height;
+ guchar *pixels;
+ gint channels;
+
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ rowstride = gdk_pixbuf_get_rowstride (pixbuf);
+ pixels = gdk_pixbuf_get_pixels (pixbuf);
+ channels = gdk_pixbuf_get_n_channels (pixbuf);
+
+ for (y = 0; y < height; y++) {
+ y_offset = y * rowstride;
+ for (x = 0; x < width; x++) {
+ offset = y_offset + x * channels;
+ if (red != 0)
+ pixels[offset] = shift_color_byte (pixels[offset], red);
+ if (green != 0)
+ pixels[offset + 1] = shift_color_byte (pixels[offset + 1], green);
+ if (blue != 0)
+ pixels[offset + 2] = shift_color_byte (pixels[offset + 2], blue);
+ if (alpha != 0 && channels >= 4)
+ pixels[offset + 3] = shift_color_byte (pixels[offset + 3], blue);
+ }
+ }
+}
+
+static void
+update_pixbufs (UmCropArea *area)
+{
+ gint width;
+ gint height;
+ GtkAllocation allocation;
+ gdouble scale;
+ GdkColor *color;
+ guint32 pixel;
+ gint dest_x, dest_y, dest_width, dest_height;
+ GtkWidget *widget;
+ GtkStyle *style;
+
+ widget = GTK_WIDGET (area);
+ gtk_widget_get_allocation (widget, &allocation);
+ style = gtk_widget_get_style (widget);
+
+ if (area->priv->pixbuf == NULL ||
+ gdk_pixbuf_get_width (area->priv->pixbuf) != allocation.width ||
+ gdk_pixbuf_get_height (area->priv->pixbuf) != allocation.height) {
+ if (area->priv->pixbuf != NULL)
+ g_object_unref (area->priv->pixbuf);
+ area->priv->pixbuf = gdk_pixbuf_new (GDK_COLORSPACE_RGB,
+ gdk_pixbuf_get_has_alpha (area->priv->browse_pixbuf),
+ 8,
+ allocation.width, allocation.height);
+
+ color = &style->bg[gtk_widget_get_state (widget)];
+ pixel = ((color->red & 0xff00) << 16) |
+ ((color->green & 0xff00) << 8) |
+ (color->blue & 0xff00);
+ gdk_pixbuf_fill (area->priv->pixbuf, pixel);
+
+ width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+ height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+
+ scale = allocation.height / (gdouble)height;
+ if (scale * width > allocation.width)
+ scale = allocation.width / (gdouble)width;
+
+ dest_width = width * scale;
+ dest_height = height * scale;
+ dest_x = (allocation.width - dest_width) / 2;
+ dest_y = (allocation.height - dest_height) / 2,
+
+ gdk_pixbuf_scale (area->priv->browse_pixbuf,
+ area->priv->pixbuf,
+ dest_x, dest_y,
+ dest_width, dest_height,
+ dest_x, dest_y,
+ scale, scale,
+ GDK_INTERP_BILINEAR);
+
+ if (area->priv->color_shifted)
+ g_object_unref (area->priv->color_shifted);
+ area->priv->color_shifted = gdk_pixbuf_copy (area->priv->pixbuf);
+ shift_colors (area->priv->color_shifted, -32, -32, -32, 0);
+
+ if (area->priv->scale == 0.0) {
+ area->priv->crop.width = 2 * area->priv->base_width / scale;
+ area->priv->crop.height = 2 * area->priv->base_height / scale;
+ area->priv->crop.x = (gdk_pixbuf_get_width (area->priv->browse_pixbuf) - area->priv->crop.width) / 2;
+ area->priv->crop.y = (gdk_pixbuf_get_height (area->priv->browse_pixbuf) - area->priv->crop.height) / 2;
+ }
+
+ area->priv->scale = scale;
+ area->priv->image.x = dest_x;
+ area->priv->image.y = dest_y;
+ area->priv->image.width = dest_width;
+ area->priv->image.height = dest_height;
+ }
+}
+
+static void
+crop_to_widget (UmCropArea *area,
+ GdkRectangle *crop)
+{
+ crop->x = area->priv->image.x + area->priv->crop.x * area->priv->scale;
+ crop->y = area->priv->image.y + area->priv->crop.y * area->priv->scale;
+ crop->width = area->priv->crop.width * area->priv->scale;
+ crop->height = area->priv->crop.height * area->priv->scale;
+}
+
+typedef enum {
+ OUTSIDE,
+ INSIDE,
+ TOP,
+ TOP_LEFT,
+ TOP_RIGHT,
+ BOTTOM,
+ BOTTOM_LEFT,
+ BOTTOM_RIGHT,
+ LEFT,
+ RIGHT
+} Location;
+
+static gboolean
+um_crop_area_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ GdkRectangle area;
+ GdkRectangle crop;
+ gint width, height;
+ UmCropArea *uarea = UM_CROP_AREA (widget);
+
+ if (uarea->priv->browse_pixbuf == NULL)
+ return FALSE;
+
+ update_pixbufs (uarea);
+
+ width = gdk_pixbuf_get_width (uarea->priv->pixbuf);
+ height = gdk_pixbuf_get_height (uarea->priv->pixbuf);
+ crop_to_widget (uarea, &crop);
+
+ gdk_cairo_set_source_pixbuf (cr, uarea->priv->color_shifted, 0, 0);
+ cairo_rectangle (cr, 0, 0, width, crop.y);
+ cairo_rectangle (cr, 0, crop.y, crop.x, crop.height);
+ cairo_rectangle (cr, crop.x + crop.width, crop.y, width - crop.x - crop.width, crop.height);
+ cairo_rectangle (cr, 0, crop.y + crop.height, width, height - crop.y - crop.height);
+ cairo_fill (cr);
+
+ gdk_cairo_set_source_pixbuf (cr, uarea->priv->pixbuf, 0, 0);
+ cairo_rectangle (cr, crop.x, crop.y, crop.width, crop.height);
+ cairo_fill (cr);
+
+ if (uarea->priv->active_region != OUTSIDE) {
+ gint x1, x2, y1, y2;
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_set_line_width (cr, 1.0);
+ x1 = crop.x + crop.width / 3.0;
+ x2 = crop.x + 2 * crop.width / 3.0;
+ y1 = crop.y + crop.height / 3.0;
+ y2 = crop.y + 2 * crop.height / 3.0;
+
+ cairo_move_to (cr, x1 + 0.5, crop.y);
+ cairo_line_to (cr, x1 + 0.5, crop.y + crop.height);
+
+ cairo_move_to (cr, x2 + 0.5, crop.y);
+ cairo_line_to (cr, x2 + 0.5, crop.y + crop.height);
+
+ cairo_move_to (cr, crop.x, y1 + 0.5);
+ cairo_line_to (cr, crop.x + crop.width, y1 + 0.5);
+
+ cairo_move_to (cr, crop.x, y2 + 0.5);
+ cairo_line_to (cr, crop.x + crop.width, y2 + 0.5);
+ cairo_stroke (cr);
+ }
+
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_set_line_width (cr, 1.0);
+ cairo_rectangle (cr,
+ crop.x + 0.5,
+ crop.y + 0.5,
+ crop.width - 1.0,
+ crop.height - 1.0);
+ cairo_stroke (cr);
+
+ cairo_set_source_rgb (cr, 1, 1, 1);
+ cairo_set_line_width (cr, 2.0);
+ cairo_rectangle (cr,
+ crop.x + 2.0,
+ crop.y + 2.0,
+ crop.width - 4.0,
+ crop.height - 4.0);
+ cairo_stroke (cr);
+
+ return FALSE;
+}
+
+typedef enum {
+ BELOW,
+ LOWER,
+ BETWEEN,
+ UPPER,
+ ABOVE
+} Range;
+
+static Range
+find_range (gint x,
+ gint min,
+ gint max)
+{
+ gint tolerance = 12;
+
+ if (x < min - tolerance)
+ return BELOW;
+ if (x <= min + tolerance)
+ return LOWER;
+ if (x < max - tolerance)
+ return BETWEEN;
+ if (x <= max + tolerance)
+ return UPPER;
+ return ABOVE;
+}
+
+static Location
+find_location (GdkRectangle *rect,
+ gint x,
+ gint y)
+{
+ Range x_range, y_range;
+ Location location[5][5] = {
+ { OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE },
+ { OUTSIDE, TOP_LEFT, TOP, TOP_RIGHT, OUTSIDE },
+ { OUTSIDE, LEFT, INSIDE, RIGHT, OUTSIDE },
+ { OUTSIDE, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT, OUTSIDE },
+ { OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE, OUTSIDE }
+ };
+
+ x_range = find_range (x, rect->x, rect->x + rect->width);
+ y_range = find_range (y, rect->y, rect->y + rect->height);
+
+ return location[y_range][x_range];
+}
+
+static void
+update_cursor (UmCropArea *area,
+ gint x,
+ gint y)
+{
+ gint cursor_type;
+ GdkRectangle crop;
+ gint region;
+
+ region = area->priv->active_region;
+ if (region == OUTSIDE) {
+ crop_to_widget (area, &crop);
+ region = find_location (&crop, x, y);
+ }
+
+ switch (region) {
+ case OUTSIDE:
+ cursor_type = GDK_LEFT_PTR;
+ break;
+ case TOP_LEFT:
+ cursor_type = GDK_TOP_LEFT_CORNER;
+ break;
+ case TOP:
+ cursor_type = GDK_TOP_SIDE;
+ break;
+ case TOP_RIGHT:
+ cursor_type = GDK_TOP_RIGHT_CORNER;
+ break;
+ case LEFT:
+ cursor_type = GDK_LEFT_SIDE;
+ break;
+ case INSIDE:
+ cursor_type = GDK_FLEUR;
+ break;
+ case RIGHT:
+ cursor_type = GDK_RIGHT_SIDE;
+ break;
+ case BOTTOM_LEFT:
+ cursor_type = GDK_BOTTOM_LEFT_CORNER;
+ break;
+ case BOTTOM:
+ cursor_type = GDK_BOTTOM_SIDE;
+ break;
+ case BOTTOM_RIGHT:
+ cursor_type = GDK_BOTTOM_RIGHT_CORNER;
+ break;
+ }
+
+ if (cursor_type != area->priv->current_cursor) {
+ GdkCursor *cursor = gdk_cursor_new (cursor_type);
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (area)), cursor);
+ gdk_cursor_unref (cursor);
+ area->priv->current_cursor = cursor_type;
+ }
+}
+
+static int
+eval_radial_line (gdouble center_x, gdouble center_y,
+ gdouble bounds_x, gdouble bounds_y,
+ gdouble user_x)
+{
+ gdouble decision_slope;
+ gdouble decision_intercept;
+
+ decision_slope = (bounds_y - center_y) / (bounds_x - center_x);
+ decision_intercept = bounds_y = -(decision_slope * bounds_x);
+
+ return (int) (decision_slope * user_x + decision_intercept);
+}
+
+static gboolean
+um_crop_area_motion_notify_event (GtkWidget *widget,
+ GdkEventMotion *event)
+{
+ UmCropArea *area = UM_CROP_AREA (widget);
+ gint x, y;
+ gint delta_x, delta_y;
+ gint width, height;
+ gint adj_width, adj_height;
+ gint pb_width, pb_height;
+ GdkRectangle damage;
+ gint left, right, top, bottom;
+ gdouble new_width, new_height;
+ gdouble center_x, center_y;
+ gint min_width, min_height;
+
+ if (area->priv->browse_pixbuf == NULL)
+ return FALSE;
+
+ update_cursor (area, event->x, event->y);
+
+ crop_to_widget (area, &damage);
+ gtk_widget_queue_draw_area (widget,
+ damage.x - 1, damage.y - 1,
+ damage.width + 2, damage.height + 2);
+
+ pb_width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+ pb_height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+
+ x = (event->x - area->priv->image.x) / area->priv->scale;
+ y = (event->y - area->priv->image.y) / area->priv->scale;
+
+ delta_x = x - area->priv->last_press_x;
+ delta_y = y - area->priv->last_press_y;
+ area->priv->last_press_x = x;
+ area->priv->last_press_y = y;
+
+ left = area->priv->crop.x;
+ right = area->priv->crop.x + area->priv->crop.width - 1;
+ top = area->priv->crop.y;
+ bottom = area->priv->crop.y + area->priv->crop.height - 1;
+
+ center_x = (left + right) / 2.0;
+ center_y = (top + bottom) / 2.0;
+
+ switch (area->priv->active_region) {
+ case INSIDE:
+ width = right - left + 1;
+ height = bottom - top + 1;
+
+ left += delta_x;
+ right += delta_x;
+ top += delta_y;
+ bottom += delta_y;
+
+ if (left < 0)
+ left = 0;
+ if (top < 0)
+ top = 0;
+ if (right > pb_width)
+ right = pb_width;
+ if (bottom > pb_height)
+ bottom = pb_height;
+
+ adj_width = right - left + 1;
+ adj_height = bottom - top + 1;
+ if (adj_width != width) {
+ if (delta_x < 0)
+ right = left + width - 1;
+ else
+ left = right - width + 1;
+ }
+ if (adj_height != height) {
+ if (delta_y < 0)
+ bottom = top + height - 1;
+ else
+ top = bottom - height + 1;
+ }
+
+ break;
+
+ case TOP_LEFT:
+ if (area->priv->aspect < 0) {
+ top = y;
+ left = x;
+ }
+ else if (y < eval_radial_line (center_x, center_y, left, top, x)) {
+ top = y;
+ new_width = (bottom - top) * area->priv->aspect;
+ left = right - new_width;
+ }
+ else {
+ left = x;
+ new_height = (right - left) / area->priv->aspect;
+ top = bottom - new_height;
+ }
+ break;
+
+ case TOP:
+ top = y;
+ if (area->priv->aspect > 0) {
+ new_width = (bottom - top) * area->priv->aspect;
+ right = left + new_width;
+ }
+ break;
+
+ case TOP_RIGHT:
+ if (area->priv->aspect < 0) {
+ top = y;
+ right = x;
+ }
+ else if (y < eval_radial_line (center_x, center_y, right, top, x)) {
+ top = y;
+ new_width = (bottom - top) * area->priv->aspect;
+ right = left + new_width;
+ }
+ else {
+ right = x;
+ new_height = (right - left) / area->priv->aspect;
+ top = bottom - new_height;
+ }
+ break;
+
+ case LEFT:
+ left = x;
+ if (area->priv->aspect > 0) {
+ new_height = (right - left) / area->priv->aspect;
+ bottom = top + new_height;
+ }
+ break;
+
+ case BOTTOM_LEFT:
+ if (area->priv->aspect < 0) {
+ bottom = y;
+ left = x;
+ }
+ else if (y < eval_radial_line (center_x, center_y, left, bottom, x)) {
+ left = x;
+ new_height = (right - left) / area->priv->aspect;
+ bottom = top + new_height;
+ }
+ else {
+ bottom = y;
+ new_width = (bottom - top) * area->priv->aspect;
+ left = right - new_width;
+ }
+ break;
+
+ case RIGHT:
+ right = x;
+ if (area->priv->aspect > 0) {
+ new_height = (right - left) / area->priv->aspect;
+ bottom = top + new_height;
+ }
+ break;
+
+ case BOTTOM_RIGHT:
+ if (area->priv->aspect < 0) {
+ bottom = y;
+ right = x;
+ }
+ else if (y < eval_radial_line (center_x, center_y, right, bottom, x)) {
+ right = x;
+ new_height = (right - left) / area->priv->aspect;
+ bottom = top + new_height;
+ }
+ else {
+ bottom = y;
+ new_width = (bottom - top) * area->priv->aspect;
+ right = left + new_width;
+ }
+ break;
+
+ case BOTTOM:
+ bottom = y;
+ if (area->priv->aspect > 0) {
+ new_width = (bottom - top) * area->priv->aspect;
+ right= left + new_width;
+ }
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ min_width = area->priv->base_width / area->priv->scale;
+ min_height = area->priv->base_height / area->priv->scale;
+
+ width = right - left + 1;
+ height = bottom - top + 1;
+ if (area->priv->aspect < 0) {
+ if (left < 0)
+ left = 0;
+ if (top < 0)
+ top = 0;
+ if (right > pb_width)
+ right = pb_width;
+ if (bottom > pb_height)
+ bottom = pb_height;
+
+ width = right - left + 1;
+ height = bottom - top + 1;
+
+ switch (area->priv->active_region) {
+ case LEFT:
+ case TOP_LEFT:
+ case BOTTOM_LEFT:
+ if (width < min_width)
+ left = right - min_width;
+ break;
+ case RIGHT:
+ case TOP_RIGHT:
+ case BOTTOM_RIGHT:
+ if (width < min_width)
+ right = left + min_width;
+ break;
+
+ default: ;
+ }
+
+ switch (area->priv->active_region) {
+ case TOP:
+ case TOP_LEFT:
+ case TOP_RIGHT:
+ if (height < min_height)
+ top = bottom - min_height;
+ break;
+ case BOTTOM:
+ case BOTTOM_LEFT:
+ case BOTTOM_RIGHT:
+ if (height < min_height)
+ bottom = top + min_height;
+ break;
+
+ default: ;
+ }
+ }
+ else {
+ if (left < 0 || top < 0 ||
+ right > pb_width || bottom > pb_height ||
+ width < min_width || height < min_height) {
+ left = area->priv->crop.x;
+ right = area->priv->crop.x + area->priv->crop.width - 1;
+ top = area->priv->crop.y;
+ bottom = area->priv->crop.y + area->priv->crop.height - 1;
+ }
+ }
+
+ area->priv->crop.x = left;
+ area->priv->crop.y = top;
+ area->priv->crop.width = right - left + 1;
+ area->priv->crop.height = bottom - top + 1;
+
+ crop_to_widget (area, &damage);
+ gtk_widget_queue_draw_area (widget,
+ damage.x - 1, damage.y - 1,
+ damage.width + 2, damage.height + 2);
+
+ return FALSE;
+}
+
+static gboolean
+um_crop_area_button_press_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ UmCropArea *area = UM_CROP_AREA (widget);
+ GdkRectangle crop;
+
+ if (area->priv->browse_pixbuf == NULL)
+ return FALSE;
+
+ crop_to_widget (area, &crop);
+
+ area->priv->last_press_x = (event->x - area->priv->image.x) / area->priv->scale;
+ area->priv->last_press_y = (event->y - area->priv->image.y) / area->priv->scale;
+ area->priv->active_region = find_location (&crop, event->x, event->y);
+
+ gtk_widget_queue_draw_area (widget,
+ crop.x - 1, crop.y - 1,
+ crop.width + 2, crop.height + 2);
+
+ return FALSE;
+}
+
+static gboolean
+um_crop_area_button_release_event (GtkWidget *widget,
+ GdkEventButton *event)
+{
+ UmCropArea *area = UM_CROP_AREA (widget);
+ GdkRectangle crop;
+
+ if (area->priv->browse_pixbuf == NULL)
+ return FALSE;
+
+ crop_to_widget (area, &crop);
+
+ area->priv->last_press_x = -1;
+ area->priv->last_press_y = -1;
+ area->priv->active_region = OUTSIDE;
+
+ gtk_widget_queue_draw_area (widget,
+ crop.x - 1, crop.y - 1,
+ crop.width + 2, crop.height + 2);
+
+ return FALSE;
+}
+
+static void
+um_crop_area_finalize (GObject *object)
+{
+ UmCropArea *area = UM_CROP_AREA (object);
+
+ if (area->priv->browse_pixbuf) {
+ g_object_unref (area->priv->browse_pixbuf);
+ area->priv->browse_pixbuf = NULL;
+ }
+ if (area->priv->pixbuf) {
+ g_object_unref (area->priv->pixbuf);
+ area->priv->pixbuf = NULL;
+ }
+ if (area->priv->color_shifted) {
+ g_object_unref (area->priv->color_shifted);
+ area->priv->color_shifted = NULL;
+ }
+}
+
+static void
+um_crop_area_class_init (UmCropAreaClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = um_crop_area_finalize;
+ widget_class->draw = um_crop_area_draw;
+ widget_class->button_press_event = um_crop_area_button_press_event;
+ widget_class->button_release_event = um_crop_area_button_release_event;
+ widget_class->motion_notify_event = um_crop_area_motion_notify_event;
+
+ g_type_class_add_private (klass, sizeof (UmCropAreaPrivate));
+}
+
+static void
+um_crop_area_init (UmCropArea *area)
+{
+ area->priv = (G_TYPE_INSTANCE_GET_PRIVATE ((area), UM_TYPE_CROP_AREA,
+ UmCropAreaPrivate));
+
+ gtk_widget_add_events (GTK_WIDGET (area), GDK_POINTER_MOTION_MASK |
+ GDK_BUTTON_PRESS_MASK |
+ GDK_BUTTON_RELEASE_MASK);
+
+ area->priv->scale = 0.0;
+ area->priv->image.x = 0;
+ area->priv->image.y = 0;
+ area->priv->image.width = 0;
+ area->priv->image.height = 0;
+ area->priv->active_region = OUTSIDE;
+ area->priv->base_width = 48;
+ area->priv->base_height = 48;
+ area->priv->aspect = 1;
+}
+
+GtkWidget *
+um_crop_area_new (void)
+{
+ return g_object_new (UM_TYPE_CROP_AREA, NULL);
+}
+
+GdkPixbuf *
+um_crop_area_get_picture (UmCropArea *area)
+{
+ gint width, height;
+
+ width = gdk_pixbuf_get_width (area->priv->browse_pixbuf);
+ height = gdk_pixbuf_get_height (area->priv->browse_pixbuf);
+ width = MIN (area->priv->crop.width, width - area->priv->crop.x);
+ height = MIN (area->priv->crop.height, height - area->priv->crop.y);
+
+ return gdk_pixbuf_new_subpixbuf (area->priv->browse_pixbuf,
+ area->priv->crop.x,
+ area->priv->crop.y,
+ width, height);
+}
+
+void
+um_crop_area_set_picture (UmCropArea *area,
+ GdkPixbuf *pixbuf)
+{
+ int width;
+ int height;
+
+ if (area->priv->browse_pixbuf) {
+ g_object_unref (area->priv->browse_pixbuf);
+ area->priv->browse_pixbuf = NULL;
+ }
+ if (pixbuf) {
+ area->priv->browse_pixbuf = g_object_ref (pixbuf);
+ width = gdk_pixbuf_get_width (pixbuf);
+ height = gdk_pixbuf_get_height (pixbuf);
+ } else {
+ width = 0;
+ height = 0;
+ }
+
+ area->priv->crop.width = 2 * area->priv->base_width;
+ area->priv->crop.height = 2 * area->priv->base_height;
+ area->priv->crop.x = (width - area->priv->crop.width) / 2;
+ area->priv->crop.y = (height - area->priv->crop.height) / 2;
+
+ area->priv->scale = 0.0;
+ area->priv->image.x = 0;
+ area->priv->image.y = 0;
+ area->priv->image.width = 0;
+ area->priv->image.height = 0;
+
+ gtk_widget_queue_draw (GTK_WIDGET (area));
+}
+
+void
+um_crop_area_set_min_size (UmCropArea *area,
+ gint width,
+ gint height)
+{
+ area->priv->base_width = width;
+ area->priv->base_height = height;
+
+ if (area->priv->aspect > 0) {
+ area->priv->aspect = area->priv->base_width / (gdouble)area->priv->base_height;
+ }
+}
+
+void
+um_crop_area_set_constrain_aspect (UmCropArea *area,
+ gboolean constrain)
+{
+ if (constrain) {
+ area->priv->aspect = area->priv->base_width / (gdouble)area->priv->base_height;
+ }
+ else {
+ area->priv->aspect = -1;
+ }
+}
+
diff --git a/panels/user-accounts/um-crop-area.h b/panels/user-accounts/um-crop-area.h
new file mode 100644
index 000000000..89929577e
--- /dev/null
+++ b/panels/user-accounts/um-crop-area.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright © 2009 Bastien Nocera
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 .
+ */
+
+#ifndef _UM_CROP_AREA_H_
+#define _UM_CROP_AREA_H_
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_CROP_AREA (um_crop_area_get_type ())
+#define UM_CROP_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_CROP_AREA, \
+ UmCropArea))
+#define UM_CROP_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_CROP_AREA, \
+ UmCropAreaClass))
+#define UM_IS_CROP_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_CROP_AREA))
+#define UM_IS_CROP_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_CROP_AREA))
+#define UM_CROP_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_CROP_AREA, \
+ UmCropAreaClass))
+
+typedef struct _UmCropAreaClass UmCropAreaClass;
+typedef struct _UmCropArea UmCropArea;
+typedef struct _UmCropAreaPrivate UmCropAreaPrivate;
+
+struct _UmCropAreaClass {
+ GtkDrawingAreaClass parent_class;
+};
+
+struct _UmCropArea {
+ GtkDrawingArea parent_instance;
+ UmCropAreaPrivate *priv;
+};
+
+GType um_crop_area_get_type (void) G_GNUC_CONST;
+
+GtkWidget *um_crop_area_new (void);
+GdkPixbuf *um_crop_area_get_picture (UmCropArea *area);
+void um_crop_area_set_picture (UmCropArea *area,
+ GdkPixbuf *pixbuf);
+void um_crop_area_set_min_size (UmCropArea *area,
+ gint width,
+ gint height);
+void um_crop_area_set_constrain_aspect (UmCropArea *area,
+ gboolean constrain);
+
+G_END_DECLS
+
+#endif /* _UM_CROP_AREA_H_ */
diff --git a/panels/user-accounts/um-editable-button.c b/panels/user-accounts/um-editable-button.c
new file mode 100644
index 000000000..321245999
--- /dev/null
+++ b/panels/user-accounts/um-editable-button.c
@@ -0,0 +1,403 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include
+#include "um-editable-button.h"
+
+#define EMPTY_TEXT "\xe2\x80\x94"
+
+struct _UmEditableButtonPrivate {
+ GtkNotebook *notebook;
+ GtkLabel *label;
+ GtkButton *button;
+
+ gchar *text;
+ gboolean editable;
+ gint weight;
+ gboolean weight_set;
+ gdouble scale;
+ gboolean scale_set;
+};
+
+#define UM_EDITABLE_BUTTON_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), UM_TYPE_EDITABLE_BUTTON, UmEditableButtonPrivate))
+
+enum {
+ PROP_0,
+ PROP_TEXT,
+ PROP_EDITABLE,
+ PROP_SCALE,
+ PROP_SCALE_SET,
+ PROP_WEIGHT,
+ PROP_WEIGHT_SET
+};
+
+enum {
+ START_EDITING,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (UmEditableButton, um_editable_button, GTK_TYPE_ALIGNMENT);
+
+void
+um_editable_button_set_text (UmEditableButton *button,
+ const gchar *text)
+{
+ UmEditableButtonPrivate *priv;
+ gchar *tmp;
+ GtkWidget *label;
+
+ priv = button->priv;
+
+ tmp = g_strdup (text);
+ g_free (priv->text);
+ priv->text = tmp;
+
+ if (tmp == NULL || tmp[0] == '\0')
+ tmp = EMPTY_TEXT;
+
+ gtk_label_set_text (priv->label, tmp);
+ label = gtk_bin_get_child (GTK_BIN (priv->button));
+ gtk_label_set_text (GTK_LABEL (label), tmp);
+
+ g_object_notify (G_OBJECT (button), "text");
+}
+
+const gchar *
+um_editable_button_get_text (UmEditableButton *button)
+{
+ return button->priv->text;
+}
+
+void
+um_editable_button_set_editable (UmEditableButton *button,
+ gboolean editable)
+{
+ UmEditableButtonPrivate *priv;
+
+ priv = button->priv;
+
+ if (priv->editable != editable) {
+ priv->editable = editable;
+
+ gtk_notebook_set_current_page (priv->notebook, editable ? 1 : 0);
+
+ g_object_notify (G_OBJECT (button), "editable");
+ }
+}
+
+gboolean
+um_editable_button_get_editable (UmEditableButton *button)
+{
+ return button->priv->editable;
+}
+
+static void
+update_fonts (UmEditableButton *button)
+{
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+ GtkWidget *label;
+
+ UmEditableButtonPrivate *priv = button->priv;
+
+ attrs = pango_attr_list_new ();
+ if (priv->scale_set) {
+ attr = pango_attr_scale_new (priv->scale);
+ pango_attr_list_insert (attrs, attr);
+ }
+ if (priv->weight_set) {
+ attr = pango_attr_weight_new (priv->weight);
+ pango_attr_list_insert (attrs, attr);
+ }
+
+ gtk_label_set_attributes (priv->label, attrs);
+
+ label = gtk_bin_get_child (GTK_BIN (priv->button));
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+
+ pango_attr_list_unref (attrs);
+}
+
+void
+um_editable_button_set_weight (UmEditableButton *button,
+ gint weight)
+{
+ UmEditableButtonPrivate *priv = button->priv;
+
+ if (priv->weight == weight && priv->weight_set)
+ return;
+
+ priv->weight = weight;
+ priv->weight_set = TRUE;
+
+ update_fonts (button);
+
+ g_object_notify (G_OBJECT (button), "weight");
+ g_object_notify (G_OBJECT (button), "weight-set");
+}
+
+gint
+um_editable_button_get_weight (UmEditableButton *button)
+{
+ return button->priv->weight;
+}
+
+void
+um_editable_button_set_scale (UmEditableButton *button,
+ gdouble scale)
+{
+ UmEditableButtonPrivate *priv = button->priv;
+
+ if (priv->scale == scale && priv->scale_set)
+ return;
+
+ priv->scale = scale;
+ priv->scale_set = TRUE;
+
+ update_fonts (button);
+
+ g_object_notify (G_OBJECT (button), "scale");
+ g_object_notify (G_OBJECT (button), "scale-set");
+}
+
+gdouble
+um_editable_button_get_scale (UmEditableButton *button)
+{
+ return button->priv->scale;
+}
+
+static void
+um_editable_button_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableButton *button = UM_EDITABLE_BUTTON (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ um_editable_button_set_text (button, g_value_get_string (value));
+ break;
+ case PROP_EDITABLE:
+ um_editable_button_set_editable (button, g_value_get_boolean (value));
+ break;
+ case PROP_WEIGHT:
+ um_editable_button_set_weight (button, g_value_get_int (value));
+ break;
+ case PROP_WEIGHT_SET:
+ button->priv->weight_set = g_value_get_boolean (value);
+ break;
+ case PROP_SCALE:
+ um_editable_button_set_scale (button, g_value_get_double (value));
+ break;
+ case PROP_SCALE_SET:
+ button->priv->scale_set = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_button_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableButton *button = UM_EDITABLE_BUTTON (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ g_value_set_string (value,
+ um_editable_button_get_text (button));
+ break;
+ case PROP_EDITABLE:
+ g_value_set_boolean (value,
+ um_editable_button_get_editable (button));
+ break;
+ case PROP_WEIGHT:
+ g_value_set_int (value,
+ um_editable_button_get_weight (button));
+ break;
+ case PROP_WEIGHT_SET:
+ g_value_set_boolean (value,
+ button->priv->weight_set);
+ break;
+ case PROP_SCALE:
+ g_value_set_double (value,
+ um_editable_button_get_scale (button));
+ break;
+ case PROP_SCALE_SET:
+ g_value_set_boolean (value,
+ button->priv->scale_set);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_button_finalize (GObject *object)
+{
+ UmEditableButton *button = (UmEditableButton*)object;
+
+ g_free (button->priv->text);
+
+ G_OBJECT_CLASS (um_editable_button_parent_class)->finalize (object);
+}
+
+static void
+um_editable_button_class_init (UmEditableButtonClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->set_property = um_editable_button_set_property;
+ object_class->get_property = um_editable_button_get_property;
+ object_class->finalize = um_editable_button_finalize;
+
+ signals[START_EDITING] =
+ g_signal_new ("start-editing",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmEditableButtonClass, start_editing),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (object_class, PROP_TEXT,
+ g_param_spec_string ("text",
+ "Text", "The text of the button",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_EDITABLE,
+ g_param_spec_boolean ("editable",
+ "Editable", "Whether the text can be edited",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WEIGHT,
+ g_param_spec_int ("weight",
+ "Font Weight", "The font weight to use",
+ 0, G_MAXINT, PANGO_WEIGHT_NORMAL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WEIGHT_SET,
+ g_param_spec_boolean ("weight-set",
+ "Font Weight Set", "Whether a font weight is set",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SCALE,
+ g_param_spec_double ("scale",
+ "Font Scale", "The font scale to use",
+ 0.0, G_MAXDOUBLE, 1.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SCALE_SET,
+ g_param_spec_boolean ("scale-set",
+ "Font Scale Set", "Whether a font scale is set",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (UmEditableButtonPrivate));
+}
+
+static void
+start_editing (UmEditableButton *button)
+{
+ g_signal_emit (button, signals[START_EDITING], 0);
+}
+
+static void
+button_clicked (GtkWidget *widget,
+ UmEditableButton *button)
+{
+ start_editing (button);
+}
+
+static void
+update_button_padding (GtkWidget *widget,
+ GtkAllocation *allocation,
+ UmEditableButton *button)
+{
+ UmEditableButtonPrivate *priv = button->priv;
+ GtkAllocation parent_allocation;
+ gint offset;
+ gint pad;
+
+ gtk_widget_get_allocation (gtk_widget_get_parent (widget), &parent_allocation);
+
+ offset = allocation->x - parent_allocation.x;
+
+ gtk_misc_get_padding (GTK_MISC (priv->label), &pad, NULL);
+ if (offset != pad)
+ gtk_misc_set_padding (GTK_MISC (priv->label), offset, 0);
+}
+
+static void
+um_editable_button_init (UmEditableButton *button)
+{
+ UmEditableButtonPrivate *priv;
+
+ priv = button->priv = UM_EDITABLE_BUTTON_GET_PRIVATE (button);
+
+ priv->weight = PANGO_WEIGHT_NORMAL;
+ priv->weight_set = FALSE;
+ priv->scale = 1.0;
+ priv->scale_set = FALSE;
+
+ priv->notebook = (GtkNotebook*)gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (priv->notebook, FALSE);
+ gtk_notebook_set_show_border (priv->notebook, FALSE);
+
+ priv->label = (GtkLabel*)gtk_label_new (EMPTY_TEXT);
+ gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->label, NULL);
+
+ priv->button = (GtkButton*)gtk_button_new_with_label (EMPTY_TEXT);
+ gtk_widget_set_receives_default ((GtkWidget*)priv->button, TRUE);
+ gtk_button_set_relief (priv->button, GTK_RELIEF_NONE);
+ gtk_button_set_alignment (priv->button, 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->button, NULL);
+ g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked), button);
+ g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->button)), "size-allocate", G_CALLBACK (update_button_padding), button);
+
+ gtk_container_add (GTK_CONTAINER (button), (GtkWidget*)priv->notebook);
+
+ gtk_widget_show ((GtkWidget*)priv->notebook);
+ gtk_widget_show ((GtkWidget*)priv->label);
+ gtk_widget_show ((GtkWidget*)priv->button);
+
+ gtk_notebook_set_current_page (priv->notebook, 0);
+}
+
+GtkWidget *
+um_editable_button_new (void)
+{
+ return (GtkWidget *) g_object_new (UM_TYPE_EDITABLE_BUTTON, NULL);
+}
diff --git a/panels/user-accounts/um-editable-button.h b/panels/user-accounts/um-editable-button.h
new file mode 100644
index 000000000..36af62212
--- /dev/null
+++ b/panels/user-accounts/um-editable-button.h
@@ -0,0 +1,72 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef _UM_EDITABLE_BUTTON_H
+#define _UM_EDITABLE_BUTTON_H
+
+#include
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_EDITABLE_BUTTON um_editable_button_get_type()
+
+#define UM_EDITABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_EDITABLE_BUTTON, UmEditableButton))
+#define UM_EDITABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_EDITABLE_BUTTON, UmEditableButtonClass))
+#define UM_IS_EDITABLE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_EDITABLE_BUTTON))
+#define UM_IS_EDITABLE_BUTTON_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_EDITABLE_BUTTON))
+#define UM_EDITABLE_BUTTON_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_EDITABLE_BUTTON, UmEditableButtonClass))
+
+typedef struct _UmEditableButton UmEditableButton;
+typedef struct _UmEditableButtonClass UmEditableButtonClass;
+typedef struct _UmEditableButtonPrivate UmEditableButtonPrivate;
+
+struct _UmEditableButton
+{
+ GtkAlignment parent;
+
+ UmEditableButtonPrivate *priv;
+};
+
+struct _UmEditableButtonClass
+{
+ GtkAlignmentClass parent_class;
+
+ void (* start_editing) (UmEditableButton *button);
+};
+
+GType um_editable_button_get_type (void) G_GNUC_CONST;
+GtkWidget *um_editable_button_new (void);
+void um_editable_button_set_text (UmEditableButton *button,
+ const gchar *text);
+const gchar *um_editable_button_get_text (UmEditableButton *button);
+void um_editable_button_set_editable (UmEditableButton *button,
+ gboolean editable);
+gboolean um_editable_button_get_editable (UmEditableButton *button);
+void um_editable_button_set_weight (UmEditableButton *button,
+ gint weight);
+gint um_editable_button_get_weight (UmEditableButton *button);
+void um_editable_button_set_scale (UmEditableButton *button,
+ gdouble scale);
+gdouble um_editable_button_get_scale (UmEditableButton *button);
+
+G_END_DECLS
+
+#endif /* _UM_EDITABLE_BUTTON_H_ */
diff --git a/panels/user-accounts/um-editable-combo.c b/panels/user-accounts/um-editable-combo.c
new file mode 100644
index 000000000..586bcdb6f
--- /dev/null
+++ b/panels/user-accounts/um-editable-combo.c
@@ -0,0 +1,439 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include
+#include "um-editable-combo.h"
+
+#define EMPTY_TEXT "\xe2\x80\x94"
+
+struct _UmEditableComboPrivate {
+ GtkNotebook *notebook;
+ GtkLabel *label;
+ GtkButton *button;
+ GtkComboBox *combo;
+ GtkWidget *toplevel;
+
+ gint active;
+ gint editable;
+ gint text_column;
+};
+
+#define UM_EDITABLE_COMBO_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), UM_TYPE_EDITABLE_COMBO, UmEditableComboPrivate))
+
+enum {
+ PROP_0,
+ PROP_EDITABLE,
+ PROP_MODEL,
+ PROP_TEXT_COLUMN
+};
+
+enum {
+ EDITING_DONE,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (UmEditableCombo, um_editable_combo, GTK_TYPE_ALIGNMENT);
+
+void
+um_editable_combo_set_editable (UmEditableCombo *combo,
+ gboolean editable)
+{
+ UmEditableComboPrivate *priv;
+
+ priv = combo->priv;
+
+ if (priv->editable != editable) {
+ priv->editable = editable;
+
+ gtk_notebook_set_current_page (priv->notebook, editable ? 1 : 0);
+
+ g_object_notify (G_OBJECT (combo), "editable");
+ }
+}
+
+gboolean
+um_editable_combo_get_editable (UmEditableCombo *combo)
+{
+ return combo->priv->editable;
+}
+
+void
+um_editable_combo_set_model (UmEditableCombo *combo,
+ GtkTreeModel *model)
+{
+ gtk_combo_box_set_model (combo->priv->combo, model);
+
+ g_object_notify (G_OBJECT (combo), "model");
+}
+
+GtkTreeModel *
+um_editable_combo_get_model (UmEditableCombo *combo)
+{
+ return gtk_combo_box_get_model (combo->priv->combo);
+}
+
+void
+um_editable_combo_set_text_column (UmEditableCombo *combo,
+ gint text_column)
+{
+ UmEditableComboPrivate *priv = combo->priv;
+ GList *cells;
+
+ if (priv->text_column == text_column)
+ return;
+
+ priv->text_column = text_column;
+
+ cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (priv->combo));
+ gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (priv->combo),
+ cells->data,
+ "text", text_column,
+ NULL);
+ g_list_free (cells);
+
+ g_object_notify (G_OBJECT (combo), "text-column");
+}
+
+gint
+um_editable_combo_get_text_column (UmEditableCombo *combo)
+{
+ return combo->priv->text_column;
+}
+
+void
+um_editable_combo_set_active (UmEditableCombo *combo,
+ gint active)
+{
+ GtkTreeModel *model;
+ GtkTreePath *path;
+ GtkTreeIter iter;
+
+ if (active == -1)
+ um_editable_combo_set_active_iter (combo, NULL);
+ else {
+ model = gtk_combo_box_get_model (combo->priv->combo);
+ path = gtk_tree_path_new_from_indices (active, -1);
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_path_free (path);
+ um_editable_combo_set_active_iter (combo, &iter);
+ }
+}
+
+void
+um_editable_combo_set_active_iter (UmEditableCombo *combo,
+ GtkTreeIter *iter)
+{
+ UmEditableComboPrivate *priv = combo->priv;
+ GtkWidget *label;
+ gchar *text;
+ GtkTreeModel *model;
+
+ gtk_combo_box_set_active_iter (priv->combo, iter);
+ priv->active = gtk_combo_box_get_active (priv->combo);
+
+ if (priv->text_column == -1)
+ return;
+
+ if (iter) {
+ model = gtk_combo_box_get_model (priv->combo);
+ gtk_tree_model_get (model, iter, priv->text_column, &text, -1);
+ }
+ else {
+ text = g_strdup (EMPTY_TEXT);
+ }
+
+ gtk_label_set_text (priv->label, text);
+ label = gtk_bin_get_child ((GtkBin*)priv->button);
+ gtk_label_set_text (GTK_LABEL (label), text);
+
+ g_free (text);
+}
+
+gboolean
+um_editable_combo_get_active_iter (UmEditableCombo *combo,
+ GtkTreeIter *iter)
+{
+ return gtk_combo_box_get_active_iter (combo->priv->combo, iter);
+}
+
+gint
+um_editable_combo_get_active (UmEditableCombo *combo)
+{
+ return combo->priv->active;
+}
+
+static void
+um_editable_combo_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableCombo *combo = UM_EDITABLE_COMBO (object);
+
+ switch (prop_id) {
+ case PROP_EDITABLE:
+ um_editable_combo_set_editable (combo, g_value_get_boolean (value));
+ break;
+ case PROP_MODEL:
+ um_editable_combo_set_model (combo, g_value_get_object (value));
+ break;
+ case PROP_TEXT_COLUMN:
+ um_editable_combo_set_text_column (combo, g_value_get_int (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_combo_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableCombo *combo = UM_EDITABLE_COMBO (object);
+
+ switch (prop_id) {
+ case PROP_EDITABLE:
+ g_value_set_boolean (value,
+ um_editable_combo_get_editable (combo));
+ break;
+ case PROP_MODEL:
+ g_value_set_object (value,
+ um_editable_combo_get_model (combo));
+ break;
+ case PROP_TEXT_COLUMN:
+ g_value_set_int (value,
+ um_editable_combo_get_text_column (combo));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_combo_class_init (UmEditableComboClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->set_property = um_editable_combo_set_property;
+ object_class->get_property = um_editable_combo_get_property;
+
+ signals[EDITING_DONE] =
+ g_signal_new ("editing-done",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmEditableComboClass, editing_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (object_class, PROP_MODEL,
+ g_param_spec_object ("model",
+ "Model", "The options to present in the combobox",
+ GTK_TYPE_TREE_MODEL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_TEXT_COLUMN,
+ g_param_spec_int ("text-column",
+ "Text Column", "The model column that contains the displayable text",
+ -1, G_MAXINT, -1,
+ G_PARAM_READWRITE));
+
+
+ g_object_class_install_property (object_class, PROP_EDITABLE,
+ g_param_spec_boolean ("editable",
+ "Editable", "Whether the text can be edited",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (UmEditableComboPrivate));
+}
+
+static void
+start_editing (UmEditableCombo *combo)
+{
+ gtk_notebook_set_current_page (combo->priv->notebook, 2);
+}
+
+static void
+stop_editing (UmEditableCombo *combo)
+{
+ um_editable_combo_set_active (combo,
+ gtk_combo_box_get_active (combo->priv->combo));
+ gtk_notebook_set_current_page (combo->priv->notebook, 1);
+
+ g_signal_emit (combo, signals[EDITING_DONE], 0);
+}
+
+static void
+cancel_editing (UmEditableCombo *combo)
+{
+ gtk_combo_box_set_active (combo->priv->combo,
+ um_editable_combo_get_active (combo));
+ gtk_notebook_set_current_page (combo->priv->notebook, 1);
+}
+
+static void
+button_clicked (GtkWidget *widget,
+ UmEditableCombo *combo)
+{
+ if (combo->priv->editable)
+ start_editing (combo);
+}
+
+static void
+combo_changed (GtkWidget *widget,
+ UmEditableCombo *combo)
+{
+ if (combo->priv->editable)
+ stop_editing (combo);
+}
+
+static gboolean
+combo_key_press (GtkWidget *widget,
+ GdkEventKey *event,
+ UmEditableCombo *combo)
+{
+ if (event->keyval == GDK_KEY_Escape) {
+ cancel_editing (combo);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void
+focus_moved (GtkWindow *window,
+ GtkWidget *widget,
+ UmEditableCombo *combo)
+{
+ if (gtk_notebook_get_current_page (combo->priv->notebook) == 2 &&
+ (!widget || !gtk_widget_is_ancestor (widget, (GtkWidget *)combo)))
+ stop_editing (combo);
+}
+
+static void
+combo_hierarchy_changed (GtkWidget *widget,
+ GtkWidget *previous_toplevel,
+ UmEditableCombo *combo)
+{
+ UmEditableComboPrivate *priv;
+ GtkWidget *toplevel;
+
+ priv = combo->priv;
+
+ toplevel = gtk_widget_get_toplevel (widget);
+ if (priv->toplevel != toplevel) {
+ if (priv->toplevel)
+ g_signal_handlers_disconnect_by_func (priv->toplevel,
+ focus_moved, combo);
+
+ if (GTK_IS_WINDOW (toplevel))
+ priv->toplevel = toplevel;
+ else
+ priv->toplevel = NULL;
+
+ if (priv->toplevel)
+ g_signal_connect (priv->toplevel, "set-focus",
+ G_CALLBACK (focus_moved), combo);
+ }
+}
+
+static void
+update_button_padding (GtkWidget *widget,
+ GtkAllocation *allocation,
+ UmEditableCombo *combo)
+{
+ UmEditableComboPrivate *priv = combo->priv;
+ GtkAllocation parent_allocation;
+ gint offset;
+ gint pad;
+
+ gtk_widget_get_allocation (gtk_widget_get_parent (widget), &parent_allocation);
+
+ offset = allocation->x - parent_allocation.x;
+
+ gtk_misc_get_padding (GTK_MISC (priv->label), &pad, NULL);
+ if (offset != pad)
+ gtk_misc_set_padding (GTK_MISC (priv->label), offset, 0);
+}
+
+static void
+um_editable_combo_init (UmEditableCombo *combo)
+{
+ UmEditableComboPrivate *priv;
+ GtkCellRenderer *cell;
+
+ priv = combo->priv = UM_EDITABLE_COMBO_GET_PRIVATE (combo);
+
+ priv->active = -1;
+ priv->text_column = -1;
+
+ priv->notebook = (GtkNotebook*)gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (priv->notebook, FALSE);
+ gtk_notebook_set_show_border (priv->notebook, FALSE);
+
+ priv->label = (GtkLabel*)gtk_label_new ("");
+ gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->label, NULL);
+
+ priv->button = (GtkButton*)gtk_button_new_with_label ("");
+ gtk_widget_set_receives_default ((GtkWidget*)priv->button, TRUE);
+ gtk_button_set_relief (priv->button, GTK_RELIEF_NONE);
+ gtk_button_set_alignment (priv->button, 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->button, NULL);
+ g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked), combo);
+
+ priv->combo = (GtkComboBox*)gtk_combo_box_new ();
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (priv->combo), cell, TRUE);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->combo, NULL);
+
+ g_signal_connect (priv->combo, "changed", G_CALLBACK (combo_changed), combo);
+ g_signal_connect (priv->combo, "key-press-event", G_CALLBACK (combo_key_press), combo);
+ g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->button)), "size-allocate", G_CALLBACK (update_button_padding), combo);
+
+
+ gtk_container_add (GTK_CONTAINER (combo), (GtkWidget*)priv->notebook);
+
+ gtk_widget_show ((GtkWidget*)priv->notebook);
+ gtk_widget_show ((GtkWidget*)priv->label);
+ gtk_widget_show ((GtkWidget*)priv->button);
+ gtk_widget_show ((GtkWidget*)priv->combo);
+
+ gtk_notebook_set_current_page (priv->notebook, 0);
+
+ /* ugly hack to catch the combo box losing focus */
+ g_signal_connect (combo, "hierarchy-changed",
+ G_CALLBACK (combo_hierarchy_changed), combo);
+}
+
+GtkWidget *
+um_editable_combo_new (void)
+{
+ return (GtkWidget *) g_object_new (UM_TYPE_EDITABLE_COMBO, NULL);
+}
diff --git a/panels/user-accounts/um-editable-combo.h b/panels/user-accounts/um-editable-combo.h
new file mode 100644
index 000000000..0d4e4a68e
--- /dev/null
+++ b/panels/user-accounts/um-editable-combo.h
@@ -0,0 +1,76 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef _UM_EDITABLE_COMBO_H
+#define _UM_EDITABLE_COMBO_H
+
+#include
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_EDITABLE_COMBO um_editable_combo_get_type()
+
+#define UM_EDITABLE_COMBO(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_EDITABLE_COMBO, UmEditableCombo))
+#define UM_EDITABLE_COMBO_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_EDITABLE_COMBO, UmEditableComboClass))
+#define UM_IS_EDITABLE_COMBO(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_EDITABLE_COMBO))
+#define UM_IS_EDITABLE_COMBO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_EDITABLE_COMBO))
+#define UM_EDITABLE_COMBO_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_EDITABLE_COMBO, UmEditableComboClass))
+
+typedef struct _UmEditableCombo UmEditableCombo;
+typedef struct _UmEditableComboClass UmEditableComboClass;
+typedef struct _UmEditableComboPrivate UmEditableComboPrivate;
+
+struct _UmEditableCombo
+{
+ GtkAlignment parent;
+
+ UmEditableComboPrivate *priv;
+};
+
+struct _UmEditableComboClass
+{
+ GtkAlignmentClass parent_class;
+
+ void (* editing_done) (UmEditableCombo *combo);
+};
+
+GType um_editable_combo_get_type (void) G_GNUC_CONST;
+GtkWidget *um_editable_combo_new (void);
+void um_editable_combo_set_editable (UmEditableCombo *combo,
+ gboolean editable);
+gboolean um_editable_combo_get_editable (UmEditableCombo *combo);
+void um_editable_combo_set_model (UmEditableCombo *combo,
+ GtkTreeModel *model);
+GtkTreeModel *um_editable_combo_get_model (UmEditableCombo *combo);
+void um_editable_combo_set_text_column (UmEditableCombo *combo,
+ gint column);
+gint um_editable_combo_get_text_column (UmEditableCombo *combo);
+gint um_editable_combo_get_active (UmEditableCombo *combo);
+void um_editable_combo_set_active (UmEditableCombo *combo,
+ gint active);
+gboolean um_editable_combo_get_active_iter (UmEditableCombo *combo,
+ GtkTreeIter *iter);
+void um_editable_combo_set_active_iter (UmEditableCombo *combo,
+ GtkTreeIter *iter);
+
+G_END_DECLS
+
+#endif /* _UM_EDITABLE_COMBO_H_ */
diff --git a/panels/user-accounts/um-editable-entry.c b/panels/user-accounts/um-editable-entry.c
new file mode 100644
index 000000000..2a183230a
--- /dev/null
+++ b/panels/user-accounts/um-editable-entry.c
@@ -0,0 +1,489 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include
+#include "um-editable-entry.h"
+
+#define EMPTY_TEXT "\xe2\x80\x94"
+
+struct _UmEditableEntryPrivate {
+ GtkNotebook *notebook;
+ GtkLabel *label;
+ GtkButton *button;
+ GtkEntry *entry;
+
+ gchar *text;
+ gboolean editable;
+ gint weight;
+ gboolean weight_set;
+ gdouble scale;
+ gboolean scale_set;
+};
+
+#define UM_EDITABLE_ENTRY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), UM_TYPE_EDITABLE_ENTRY, UmEditableEntryPrivate))
+
+enum {
+ PROP_0,
+ PROP_TEXT,
+ PROP_EDITABLE,
+ PROP_SCALE,
+ PROP_SCALE_SET,
+ PROP_WEIGHT,
+ PROP_WEIGHT_SET
+};
+
+enum {
+ EDITING_DONE,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+G_DEFINE_TYPE (UmEditableEntry, um_editable_entry, GTK_TYPE_ALIGNMENT);
+
+void
+um_editable_entry_set_text (UmEditableEntry *e,
+ const gchar *text)
+{
+ UmEditableEntryPrivate *priv;
+ gchar *tmp;
+ GtkWidget *label;
+
+ priv = e->priv;
+
+ tmp = g_strdup (text);
+ g_free (priv->text);
+ priv->text = tmp;
+
+ gtk_entry_set_text (priv->entry, tmp);
+
+ if (tmp == NULL || tmp[0] == '\0')
+ tmp = EMPTY_TEXT;
+
+ gtk_label_set_text (priv->label, tmp);
+ label = gtk_bin_get_child (GTK_BIN (priv->button));
+ gtk_label_set_text (GTK_LABEL (label), tmp);
+
+ g_object_notify (G_OBJECT (e), "text");
+}
+
+const gchar *
+um_editable_entry_get_text (UmEditableEntry *e)
+{
+ return e->priv->text;
+}
+
+void
+um_editable_entry_set_editable (UmEditableEntry *e,
+ gboolean editable)
+{
+ UmEditableEntryPrivate *priv;
+
+ priv = e->priv;
+
+ if (priv->editable != editable) {
+ priv->editable = editable;
+
+ gtk_notebook_set_current_page (priv->notebook, editable ? 1 : 0);
+
+ g_object_notify (G_OBJECT (e), "editable");
+ }
+}
+
+gboolean
+um_editable_entry_get_editable (UmEditableEntry *e)
+{
+ return e->priv->editable;
+}
+
+static void
+update_entry_font (GtkWidget *widget,
+ GtkStyle *previous_style,
+ UmEditableEntry *e)
+{
+ UmEditableEntryPrivate *priv = e->priv;
+ PangoFontDescription *desc;
+ GtkStyle *style;
+ gint size;
+
+ if (!priv->weight_set && !priv->scale_set)
+ return;
+
+ g_signal_handlers_block_by_func (widget, update_entry_font, e);
+
+ gtk_widget_modify_font (widget, NULL);
+
+ style = gtk_widget_get_style (widget);
+ desc = pango_font_description_copy (style->font_desc);
+ if (priv->weight_set)
+ pango_font_description_set_weight (desc, priv->weight);
+ if (priv->scale_set) {
+ size = pango_font_description_get_size (desc);
+ pango_font_description_set_size (desc, priv->scale * size);
+ }
+ gtk_widget_modify_font (widget, desc);
+
+ pango_font_description_free (desc);
+
+ g_signal_handlers_unblock_by_func (widget, update_entry_font, e);
+}
+
+static void
+update_fonts (UmEditableEntry *e)
+{
+ PangoAttrList *attrs;
+ PangoAttribute *attr;
+ GtkWidget *label;
+
+ UmEditableEntryPrivate *priv = e->priv;
+
+ attrs = pango_attr_list_new ();
+ if (priv->scale_set) {
+ attr = pango_attr_scale_new (priv->scale);
+ pango_attr_list_insert (attrs, attr);
+ }
+ if (priv->weight_set) {
+ attr = pango_attr_weight_new (priv->weight);
+ pango_attr_list_insert (attrs, attr);
+ }
+
+ gtk_label_set_attributes (priv->label, attrs);
+
+ label = gtk_bin_get_child (GTK_BIN (priv->button));
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+
+ pango_attr_list_unref (attrs);
+
+ update_entry_font ((GtkWidget *)priv->entry, NULL, e);
+}
+
+void
+um_editable_entry_set_weight (UmEditableEntry *e,
+ gint weight)
+{
+ UmEditableEntryPrivate *priv = e->priv;
+
+ if (priv->weight == weight && priv->weight_set)
+ return;
+
+ priv->weight = weight;
+ priv->weight_set = TRUE;
+
+ update_fonts (e);
+
+ g_object_notify (G_OBJECT (e), "weight");
+ g_object_notify (G_OBJECT (e), "weight-set");
+}
+
+gint
+um_editable_entry_get_weight (UmEditableEntry *e)
+{
+ return e->priv->weight;
+}
+
+void
+um_editable_entry_set_scale (UmEditableEntry *e,
+ gdouble scale)
+{
+ UmEditableEntryPrivate *priv = e->priv;
+
+ if (priv->scale == scale && priv->scale_set)
+ return;
+
+ priv->scale = scale;
+ priv->scale_set = TRUE;
+
+ update_fonts (e);
+
+ g_object_notify (G_OBJECT (e), "scale");
+ g_object_notify (G_OBJECT (e), "scale-set");
+}
+
+gdouble
+um_editable_entry_get_scale (UmEditableEntry *e)
+{
+ return e->priv->scale;
+}
+
+static void
+um_editable_entry_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableEntry *e = UM_EDITABLE_ENTRY (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ um_editable_entry_set_text (e, g_value_get_string (value));
+ break;
+ case PROP_EDITABLE:
+ um_editable_entry_set_editable (e, g_value_get_boolean (value));
+ break;
+ case PROP_WEIGHT:
+ um_editable_entry_set_weight (e, g_value_get_int (value));
+ break;
+ case PROP_WEIGHT_SET:
+ e->priv->weight_set = g_value_get_boolean (value);
+ break;
+ case PROP_SCALE:
+ um_editable_entry_set_scale (e, g_value_get_double (value));
+ break;
+ case PROP_SCALE_SET:
+ e->priv->scale_set = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_entry_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmEditableEntry *e = UM_EDITABLE_ENTRY (object);
+
+ switch (prop_id) {
+ case PROP_TEXT:
+ g_value_set_string (value,
+ um_editable_entry_get_text (e));
+ break;
+ case PROP_EDITABLE:
+ g_value_set_boolean (value,
+ um_editable_entry_get_editable (e));
+ break;
+ case PROP_WEIGHT:
+ g_value_set_int (value,
+ um_editable_entry_get_weight (e));
+ break;
+ case PROP_WEIGHT_SET:
+ g_value_set_boolean (value, e->priv->weight_set);
+ break;
+ case PROP_SCALE:
+ g_value_set_double (value,
+ um_editable_entry_get_scale (e));
+ break;
+ case PROP_SCALE_SET:
+ g_value_set_boolean (value, e->priv->scale_set);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_editable_entry_finalize (GObject *object)
+{
+ UmEditableEntry *e = (UmEditableEntry*)object;
+
+ g_free (e->priv->text);
+
+ G_OBJECT_CLASS (um_editable_entry_parent_class)->finalize (object);
+}
+
+static void
+um_editable_entry_class_init (UmEditableEntryClass *class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (class);
+
+ object_class->set_property = um_editable_entry_set_property;
+ object_class->get_property = um_editable_entry_get_property;
+ object_class->finalize = um_editable_entry_finalize;
+
+ signals[EDITING_DONE] =
+ g_signal_new ("editing-done",
+ G_TYPE_FROM_CLASS (class),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmEditableEntryClass, editing_done),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ g_object_class_install_property (object_class, PROP_TEXT,
+ g_param_spec_string ("text",
+ "Text", "The text of the button",
+ NULL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_EDITABLE,
+ g_param_spec_boolean ("editable",
+ "Editable", "Whether the text can be edited",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WEIGHT,
+ g_param_spec_int ("weight",
+ "Font Weight", "The font weight to use",
+ 0, G_MAXINT, PANGO_WEIGHT_NORMAL,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_WEIGHT_SET,
+ g_param_spec_boolean ("weight-set",
+ "Font Weight Set", "Whether a font weight is set",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SCALE,
+ g_param_spec_double ("scale",
+ "Font Scale", "The font scale to use",
+ 0.0, G_MAXDOUBLE, 1.0,
+ G_PARAM_READWRITE));
+
+ g_object_class_install_property (object_class, PROP_SCALE_SET,
+ g_param_spec_boolean ("scale-set",
+ "Font Scale Set", "Whether a font scale is set",
+ FALSE,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (UmEditableEntryPrivate));
+}
+
+static void
+start_editing (UmEditableEntry *e)
+{
+ gtk_notebook_set_current_page (e->priv->notebook, 2);
+}
+
+static void
+stop_editing (UmEditableEntry *e)
+{
+ um_editable_entry_set_text (e, gtk_entry_get_text (e->priv->entry));
+ gtk_notebook_set_current_page (e->priv->notebook, 1);
+ g_signal_emit (e, signals[EDITING_DONE], 0);
+}
+
+static void
+cancel_editing (UmEditableEntry *e)
+{
+ gtk_entry_set_text (e->priv->entry, um_editable_entry_get_text (e));
+ gtk_notebook_set_current_page (e->priv->notebook, 1);
+}
+
+static void
+button_clicked (GtkWidget *widget,
+ UmEditableEntry *e)
+{
+ start_editing (e);
+}
+
+static void
+entry_activated (GtkWidget *widget,
+ UmEditableEntry *e)
+{
+ stop_editing (e);
+}
+
+static gboolean
+entry_focus_out (GtkWidget *widget,
+ GdkEventFocus *event,
+ UmEditableEntry *e)
+{
+ stop_editing (e);
+ return FALSE;
+}
+
+static gboolean
+entry_key_press (GtkWidget *widget,
+ GdkEventKey *event,
+ UmEditableEntry *e)
+{
+ if (event->keyval == GDK_KEY_Escape) {
+ cancel_editing (e);
+ }
+ return FALSE;
+}
+
+static void
+update_button_padding (GtkWidget *widget,
+ GtkAllocation *allocation,
+ UmEditableEntry *e)
+{
+ UmEditableEntryPrivate *priv = e->priv;
+ GtkAllocation alloc;
+ gint offset;
+ gint pad;
+
+ gtk_widget_get_allocation (gtk_widget_get_parent (widget), &alloc);
+
+ offset = allocation->x - alloc.x;
+
+ gtk_misc_get_padding (GTK_MISC (priv->label), &pad, NULL);
+ if (offset != pad)
+ gtk_misc_set_padding (GTK_MISC (priv->label), offset, 0);
+}
+
+static void
+um_editable_entry_init (UmEditableEntry *e)
+{
+ UmEditableEntryPrivate *priv;
+
+ priv = e->priv = UM_EDITABLE_ENTRY_GET_PRIVATE (e);
+
+ priv->weight = PANGO_WEIGHT_NORMAL;
+ priv->weight_set = FALSE;
+ priv->scale = 1.0;
+ priv->scale_set = FALSE;
+
+ priv->notebook = (GtkNotebook*)gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (priv->notebook, FALSE);
+ gtk_notebook_set_show_border (priv->notebook, FALSE);
+
+ priv->label = (GtkLabel*)gtk_label_new (EMPTY_TEXT);
+ gtk_misc_set_alignment (GTK_MISC (priv->label), 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->label, NULL);
+
+ priv->button = (GtkButton*)gtk_button_new_with_label (EMPTY_TEXT);
+ gtk_widget_set_receives_default ((GtkWidget*)priv->button, TRUE);
+ gtk_button_set_relief (priv->button, GTK_RELIEF_NONE);
+ gtk_button_set_alignment (priv->button, 0.0, 0.5);
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->button, NULL);
+ g_signal_connect (priv->button, "clicked", G_CALLBACK (button_clicked), e);
+
+ priv->entry = (GtkEntry*)gtk_entry_new ();
+ gtk_notebook_append_page (priv->notebook, (GtkWidget*)priv->entry, NULL);
+
+ g_signal_connect (priv->entry, "activate", G_CALLBACK (entry_activated), e);
+ g_signal_connect (priv->entry, "focus-out-event", G_CALLBACK (entry_focus_out), e);
+ g_signal_connect (priv->entry, "key-press-event", G_CALLBACK (entry_key_press), e);
+ g_signal_connect (priv->entry, "style-set", G_CALLBACK (update_entry_font), e);
+ g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->button)), "size-allocate", G_CALLBACK (update_button_padding), e);
+
+ gtk_container_add (GTK_CONTAINER (e), (GtkWidget*)priv->notebook);
+
+ gtk_widget_show ((GtkWidget*)priv->notebook);
+ gtk_widget_show ((GtkWidget*)priv->label);
+ gtk_widget_show ((GtkWidget*)priv->button);
+ gtk_widget_show ((GtkWidget*)priv->entry);
+
+ gtk_notebook_set_current_page (priv->notebook, 0);
+}
+
+GtkWidget *
+um_editable_entry_new (void)
+{
+ return (GtkWidget *) g_object_new (UM_TYPE_EDITABLE_ENTRY, NULL);
+}
diff --git a/panels/user-accounts/um-editable-entry.h b/panels/user-accounts/um-editable-entry.h
new file mode 100644
index 000000000..1f5f3f41f
--- /dev/null
+++ b/panels/user-accounts/um-editable-entry.h
@@ -0,0 +1,72 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef _UM_EDITABLE_ENTRY_H_
+#define _UM_EDITABLE_ENTRY_H_
+
+#include
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_EDITABLE_ENTRY um_editable_entry_get_type()
+
+#define UM_EDITABLE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_EDITABLE_ENTRY, UmEditableEntry))
+#define UM_EDITABLE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_EDITABLE_ENTRY, UmEditableEntryClass))
+#define UM_IS_EDITABLE_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_EDITABLE_ENTRY))
+#define UM_IS_EDITABLE_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_EDITABLE_ENTRY))
+#define UM_EDITABLE_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_EDITABLE_ENTRY, UmEditableEntryClass))
+
+typedef struct _UmEditableEntry UmEditableEntry;
+typedef struct _UmEditableEntryClass UmEditableEntryClass;
+typedef struct _UmEditableEntryPrivate UmEditableEntryPrivate;
+
+struct _UmEditableEntry
+{
+ GtkAlignment parent;
+
+ UmEditableEntryPrivate *priv;
+};
+
+struct _UmEditableEntryClass
+{
+ GtkAlignmentClass parent_class;
+
+ void (* editing_done) (UmEditableEntry *entry);
+};
+
+GType um_editable_entry_get_type (void) G_GNUC_CONST;
+GtkWidget *um_editable_entry_new (void);
+void um_editable_entry_set_text (UmEditableEntry *entry,
+ const gchar *text);
+const gchar *um_editable_entry_get_text (UmEditableEntry *entry);
+void um_editable_entry_set_editable (UmEditableEntry *entry,
+ gboolean editable);
+gboolean um_editable_entry_get_editable (UmEditableEntry *entry);
+void um_editable_entry_set_weight (UmEditableEntry *entry,
+ gint weight);
+gint um_editable_entry_get_weight (UmEditableEntry *entry);
+void um_editable_entry_set_scale (UmEditableEntry *entry,
+ gdouble scale);
+gdouble um_editable_entry_get_scale (UmEditableEntry *entry);
+
+G_END_DECLS
+
+#endif /* _UM_EDITABLE_ENTRY_H_ */
diff --git a/panels/user-accounts/um-fingerprint-dialog.c b/panels/user-accounts/um-fingerprint-dialog.c
new file mode 100644
index 000000000..de11c95c9
--- /dev/null
+++ b/panels/user-accounts/um-fingerprint-dialog.c
@@ -0,0 +1,674 @@
+/* gnome-about-me-fingerprint.h
+ * Copyright (C) 2008 Bastien Nocera
+ *
+ * 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, 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.
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+
+#include "um-fingerprint-dialog.h"
+
+#include "fingerprint-strings.h"
+
+/* Retrieve a widget from the UI object */
+#define WID(s) GTK_WIDGET (gtk_builder_get_object (dialog, s))
+
+/* Translate fprintd strings */
+#define TR(s) dgettext("fprintd", s)
+
+/* This must match the number of images on the 2nd page in the UI file */
+#define MAX_ENROLL_STAGES 5
+
+static DBusGProxy *manager = NULL;
+static DBusGConnection *connection = NULL;
+static gboolean is_disable = FALSE;
+
+enum {
+ STATE_NONE,
+ STATE_CLAIMED,
+ STATE_ENROLLING
+};
+
+typedef struct {
+ GtkWidget *label1;
+ GtkWidget *label2;
+
+ GtkWidget *ass;
+ GtkBuilder *dialog;
+
+ DBusGProxy *device;
+ gboolean is_swipe;
+ int num_enroll_stages;
+ int num_stages_done;
+ char *name;
+ const char *finger;
+ gint state;
+} EnrollData;
+
+static void create_manager (void)
+{
+ GError *error = NULL;
+
+ connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (connection == NULL) {
+ g_warning ("Failed to connect to session bus: %s", error->message);
+ return;
+ }
+
+ manager = dbus_g_proxy_new_for_name (connection,
+ "net.reactivated.Fprint",
+ "/net/reactivated/Fprint/Manager",
+ "net.reactivated.Fprint.Manager");
+}
+
+static DBusGProxy *
+get_first_device (void)
+{
+ DBusGProxy *device;
+ char *device_str;
+
+ if (!dbus_g_proxy_call (manager, "GetDefaultDevice", NULL, G_TYPE_INVALID,
+ DBUS_TYPE_G_OBJECT_PATH, &device_str, G_TYPE_INVALID)) {
+ return NULL;
+ }
+
+ device = dbus_g_proxy_new_for_name(connection,
+ "net.reactivated.Fprint",
+ device_str,
+ "net.reactivated.Fprint.Device");
+
+ g_free (device_str);
+
+ return device;
+}
+
+static const char *
+get_reason_for_error (const char *dbus_error)
+{
+ if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.PermissionDenied"))
+ return N_("You are not allowed to access the device. Contact your system administrator.");
+ if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.AlreadyInUse"))
+ return N_("The device is already in use.");
+ if (g_str_equal (dbus_error, "net.reactivated.Fprint.Error.Internal"))
+ return N_("An internal error occurred.");
+
+ return NULL;
+}
+
+static GtkWidget *
+get_error_dialog (const char *title,
+ const char *dbus_error,
+ GtkWindow *parent)
+{
+ GtkWidget *error_dialog;
+ const char *reason;
+
+ if (dbus_error == NULL)
+ g_warning ("get_error_dialog called with reason == NULL");
+
+ error_dialog =
+ gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ "%s", title);
+ reason = get_reason_for_error (dbus_error);
+ gtk_message_dialog_format_secondary_text
+ (GTK_MESSAGE_DIALOG (error_dialog), "%s", reason ? _(reason) : _(dbus_error));
+
+ gtk_window_set_title (GTK_WINDOW (error_dialog), ""); /* as per HIG */
+ gtk_container_set_border_width (GTK_CONTAINER (error_dialog), 5);
+ gtk_dialog_set_default_response (GTK_DIALOG (error_dialog),
+ GTK_RESPONSE_OK);
+ gtk_window_set_modal (GTK_WINDOW (error_dialog), TRUE);
+ gtk_window_set_position (GTK_WINDOW (error_dialog), GTK_WIN_POS_CENTER_ON_PARENT);
+
+ return error_dialog;
+}
+
+gboolean
+set_fingerprint_label (GtkWidget *label1,
+ GtkWidget *label2)
+{
+ char **fingers;
+ DBusGProxy *device;
+ GError *error = NULL;
+
+ if (manager == NULL) {
+ create_manager ();
+ if (manager == NULL) {
+ return FALSE;
+ }
+ }
+
+ device = get_first_device ();
+ if (device == NULL)
+ return FALSE;
+
+ if (!dbus_g_proxy_call (device, "ListEnrolledFingers", &error, G_TYPE_STRING, "", G_TYPE_INVALID,
+ G_TYPE_STRV, &fingers, G_TYPE_INVALID)) {
+ if (dbus_g_error_has_name (error, "net.reactivated.Fprint.Error.NoEnrolledPrints") == FALSE) {
+ g_object_unref (device);
+ return FALSE;
+ }
+ fingers = NULL;
+ }
+
+ if (fingers == NULL || g_strv_length (fingers) == 0) {
+ is_disable = FALSE;
+ gtk_label_set_text (GTK_LABEL (label1), _("Disabled"));
+ gtk_label_set_text (GTK_LABEL (label2), _("Disabled"));
+ } else {
+ is_disable = TRUE;
+ gtk_label_set_text (GTK_LABEL (label1), _("Enabled"));
+ gtk_label_set_text (GTK_LABEL (label2), _("Enabled"));
+ }
+
+ g_strfreev (fingers);
+ g_object_unref (device);
+
+ return TRUE;
+}
+
+static void
+delete_fingerprints (void)
+{
+ DBusGProxy *device;
+
+ if (manager == NULL) {
+ create_manager ();
+ if (manager == NULL)
+ return;
+ }
+
+ device = get_first_device ();
+ if (device == NULL)
+ return;
+
+ dbus_g_proxy_call (device, "DeleteEnrolledFingers", NULL, G_TYPE_STRING, "", G_TYPE_INVALID, G_TYPE_INVALID);
+
+ g_object_unref (device);
+}
+
+static void
+delete_fingerprints_question (GtkWindow *parent,
+ GtkWidget *label1,
+ GtkWidget *label2,
+ UmUser *user)
+{
+ GtkWidget *question;
+ GtkWidget *button;
+
+ question = gtk_message_dialog_new (parent,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Delete registered fingerprints?"));
+ gtk_dialog_add_button (GTK_DIALOG (question), GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
+ gtk_window_set_modal (GTK_WINDOW (question), TRUE);
+
+ button = gtk_button_new_with_mnemonic (_("_Delete Fingerprints"));
+ gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_stock (GTK_STOCK_DELETE, GTK_ICON_SIZE_BUTTON));
+ gtk_widget_set_can_default (button, TRUE);
+ gtk_widget_show (button);
+ gtk_dialog_add_action_widget (GTK_DIALOG (question), button, GTK_RESPONSE_OK);
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (question),
+ _("Do you want to delete your registered fingerprints so fingerprint login is disabled?"));
+ gtk_container_set_border_width (GTK_CONTAINER (question), 5);
+ gtk_dialog_set_default_response (GTK_DIALOG (question), GTK_RESPONSE_OK);
+ gtk_window_set_position (GTK_WINDOW (question), GTK_WIN_POS_CENTER_ON_PARENT);
+ gtk_window_set_modal (GTK_WINDOW (question), TRUE);
+
+ if (gtk_dialog_run (GTK_DIALOG (question)) == GTK_RESPONSE_OK) {
+ delete_fingerprints ();
+ set_fingerprint_label (label1, label2);
+ }
+
+ gtk_widget_destroy (question);
+}
+
+static void
+enroll_data_destroy (EnrollData *data)
+{
+ switch (data->state) {
+ case STATE_ENROLLING:
+ dbus_g_proxy_call(data->device, "EnrollStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ /* fall-through */
+ case STATE_CLAIMED:
+ dbus_g_proxy_call(data->device, "Release", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ /* fall-through */
+ case STATE_NONE:
+ g_free (data->name);
+ g_object_unref (data->device);
+ g_object_unref (data->dialog);
+ gtk_widget_destroy (data->ass);
+
+ g_free (data);
+ }
+}
+
+static const char *
+selected_finger (GtkBuilder *dialog)
+{
+ int index;
+
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (WID ("radiobutton1")))) {
+ gtk_widget_set_sensitive (WID ("finger_combobox"), FALSE);
+ return "right-index-finger";
+ }
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (WID ("radiobutton2")))) {
+ gtk_widget_set_sensitive (WID ("finger_combobox"), FALSE);
+ return "left-index-finger";
+ }
+ gtk_widget_set_sensitive (WID ("finger_combobox"), TRUE);
+ index = gtk_combo_box_get_active (GTK_COMBO_BOX (WID ("finger_combobox")));
+ switch (index) {
+ case 0:
+ return "left-thumb";
+ case 1:
+ return "left-middle-finger";
+ case 2:
+ return "left-ring-finger";
+ case 3:
+ return "left-little-finger";
+ case 4:
+ return "right-thumb";
+ case 5:
+ return "right-middle-finger";
+ case 6:
+ return "right-ring-finger";
+ case 7:
+ return "right-little-finger";
+ default:
+ g_assert_not_reached ();
+ }
+
+ return NULL;
+}
+
+static void
+finger_radio_button_toggled (GtkToggleButton *button, EnrollData *data)
+{
+ GtkBuilder *dialog = data->dialog;
+ char *msg;
+
+ data->finger = selected_finger (data->dialog);
+
+ msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
+ gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
+ g_free (msg);
+}
+
+static void
+finger_combobox_changed (GtkComboBox *combobox, EnrollData *data)
+{
+ GtkBuilder *dialog = data->dialog;
+ char *msg;
+
+ data->finger = selected_finger (data->dialog);
+
+ msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
+ gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
+ g_free (msg);
+}
+
+static void
+assistant_cancelled (GtkAssistant *ass, EnrollData *data)
+{
+ GtkWidget *label1, *label2;
+
+ label1 = data->label1;
+ label2 = data->label2;
+
+ enroll_data_destroy (data);
+ set_fingerprint_label (label1, label2);
+}
+
+static void
+enroll_result (GObject *object, const char *result, gboolean done, EnrollData *data)
+{
+ GtkBuilder *dialog = data->dialog;
+ char *msg;
+
+ if (g_str_equal (result, "enroll-completed") || g_str_equal (result, "enroll-stage-passed")) {
+ char *name, *path;
+
+ data->num_stages_done++;
+ name = g_strdup_printf ("image%d", data->num_stages_done);
+ path = g_build_filename (UM_PIXMAP_DIR, "print_ok.png", NULL);
+ gtk_image_set_from_file (GTK_IMAGE (WID (name)), path);
+ g_free (name);
+ g_free (path);
+ }
+ if (g_str_equal (result, "enroll-completed")) {
+ gtk_label_set_text (GTK_LABEL (WID ("status-label")), _("Done!"));
+ gtk_assistant_set_page_complete (GTK_ASSISTANT (data->ass), WID ("page2"), TRUE);
+ }
+
+ if (done != FALSE) {
+ dbus_g_proxy_call(data->device, "EnrollStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ data->state = STATE_CLAIMED;
+ if (g_str_equal (result, "enroll-completed") == FALSE) {
+ /* The enrollment failed, restart it */
+ dbus_g_proxy_call(data->device, "EnrollStart", NULL, G_TYPE_STRING, data->finger, G_TYPE_INVALID, G_TYPE_INVALID);
+ data->state = STATE_ENROLLING;
+ result = "enroll-retry-scan";
+ } else {
+ return;
+ }
+ }
+
+ msg = g_strdup_printf (TR(enroll_result_str_to_msg (result, data->is_swipe)), data->name);
+ gtk_label_set_text (GTK_LABEL (WID ("status-label")), msg);
+ g_free (msg);
+}
+
+static void
+assistant_prepare (GtkAssistant *ass, GtkWidget *page, EnrollData *data)
+{
+ const char *name;
+
+ name = g_object_get_data (G_OBJECT (page), "name");
+ if (name == NULL)
+ return;
+
+ if (g_str_equal (name, "enroll")) {
+ DBusGProxy *p;
+ GError *error = NULL;
+ GtkBuilder *dialog = data->dialog;
+ char *path;
+ guint i;
+ GValue value = { 0, };
+
+ if (!dbus_g_proxy_call (data->device, "Claim", &error, G_TYPE_STRING, "", G_TYPE_INVALID, G_TYPE_INVALID)) {
+ GtkWidget *d;
+ char *msg;
+
+ /* translators:
+ * The variable is the name of the device, for example:
+ * "Could you not access "Digital Persona U.are.U 4000/4000B" device */
+ msg = g_strdup_printf (_("Could not access '%s' device"), data->name);
+ d = get_error_dialog (msg, dbus_g_error_get_name (error), GTK_WINDOW (data->ass));
+ g_error_free (error);
+ gtk_dialog_run (GTK_DIALOG (d));
+ gtk_widget_destroy (d);
+ g_free (msg);
+
+ enroll_data_destroy (data);
+
+ return;
+ }
+ data->state = STATE_CLAIMED;
+
+ p = dbus_g_proxy_new_from_proxy (data->device, "org.freedesktop.DBus.Properties", NULL);
+ if (!dbus_g_proxy_call (p, "Get", NULL, G_TYPE_STRING, "net.reactivated.Fprint.Device", G_TYPE_STRING, "num-enroll-stages", G_TYPE_INVALID,
+ G_TYPE_VALUE, &value, G_TYPE_INVALID) || g_value_get_int (&value) < 1) {
+ GtkWidget *d;
+ char *msg;
+
+ /* translators:
+ * The variable is the name of the device, for example:
+ * "Could you not access "Digital Persona U.are.U 4000/4000B" device */
+ msg = g_strdup_printf (_("Could not access '%s' device"), data->name);
+ d = get_error_dialog (msg, "net.reactivated.Fprint.Error.Internal", GTK_WINDOW (data->ass));
+ gtk_dialog_run (GTK_DIALOG (d));
+ gtk_widget_destroy (d);
+ g_free (msg);
+
+ enroll_data_destroy (data);
+
+ g_object_unref (p);
+ return;
+ }
+ g_object_unref (p);
+
+ data->num_enroll_stages = g_value_get_int (&value);
+
+ /* Hide the extra "bulbs" if not needed */
+ for (i = MAX_ENROLL_STAGES; i > data->num_enroll_stages; i--) {
+ char *name;
+
+ name = g_strdup_printf ("image%d", i);
+ gtk_widget_hide (WID (name));
+ g_free (name);
+ }
+ /* And set the right image */
+ {
+ char *filename;
+
+ filename = g_strdup_printf ("%s.png", data->finger);
+ path = g_build_filename (UM_PIXMAP_DIR, filename, NULL);
+ g_free (filename);
+ }
+ for (i = 1; i <= data->num_enroll_stages; i++) {
+ char *name;
+ name = g_strdup_printf ("image%d", i);
+ gtk_image_set_from_file (GTK_IMAGE (WID (name)), path);
+ g_free (name);
+ }
+ g_free (path);
+
+ dbus_g_proxy_add_signal(data->device, "EnrollStatus", G_TYPE_STRING, G_TYPE_BOOLEAN, NULL);
+ dbus_g_proxy_connect_signal(data->device, "EnrollStatus", G_CALLBACK(enroll_result), data, NULL);
+
+ if (!dbus_g_proxy_call(data->device, "EnrollStart", &error, G_TYPE_STRING, data->finger, G_TYPE_INVALID, G_TYPE_INVALID)) {
+ GtkWidget *d;
+ char *msg;
+
+ /* translators:
+ * The variable is the name of the device, for example:
+ * "Could you not access "Digital Persona U.are.U 4000/4000B" device */
+ msg = g_strdup_printf (_("Could not start finger capture on '%s' device"), data->name);
+ d = get_error_dialog (msg, dbus_g_error_get_name (error), GTK_WINDOW (data->ass));
+ g_error_free (error);
+ gtk_dialog_run (GTK_DIALOG (d));
+ gtk_widget_destroy (d);
+ g_free (msg);
+
+ enroll_data_destroy (data);
+
+ return;
+ }
+ data->state = STATE_ENROLLING;;
+ } else {
+ if (data->state == STATE_ENROLLING) {
+ dbus_g_proxy_call(data->device, "EnrollStop", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ data->state = STATE_CLAIMED;
+ }
+ if (data->state == STATE_CLAIMED) {
+ dbus_g_proxy_call(data->device, "Release", NULL, G_TYPE_INVALID, G_TYPE_INVALID);
+ data->state = STATE_NONE;
+ }
+ }
+}
+
+static void
+align_image (GtkWidget *child, gpointer data)
+{
+ if (GTK_IS_IMAGE (child)) {
+ gtk_misc_set_alignment (GTK_MISC (child), 0, 0.5);
+ gtk_misc_set_padding (GTK_MISC (child), 10, 10);
+ }
+
+ if (GTK_IS_LABEL (child)) {
+ gtk_label_set_use_markup (GTK_LABEL (child), TRUE);
+ gtk_widget_modify_font (child, NULL);
+ gtk_misc_set_padding (GTK_MISC (child), 68, 10);
+ }
+}
+
+static void
+enroll_fingerprints (GtkWindow *parent,
+ GtkWidget *label1,
+ GtkWidget *label2,
+ UmUser *user)
+{
+ DBusGProxy *device, *p;
+ GHashTable *props;
+ GtkBuilder *dialog;
+ EnrollData *data;
+ GtkWidget *ass;
+ const char *filename;
+ char *msg;
+ GError *error = NULL;
+ GdkPixbuf *pixbuf;
+ gchar *title;
+ GtkStyle *style;
+
+ device = NULL;
+
+ if (manager == NULL) {
+ create_manager ();
+ if (manager != NULL)
+ device = get_first_device ();
+ } else {
+ device = get_first_device ();
+ }
+
+ if (manager == NULL || device == NULL) {
+ GtkWidget *d;
+
+ d = get_error_dialog (_("Could not access any fingerprint readers"),
+ _("Please contact your system administrator for help."),
+ parent);
+ gtk_dialog_run (GTK_DIALOG (d));
+ gtk_widget_destroy (d);
+ return;
+ }
+
+ data = g_new0 (EnrollData, 1);
+ data->device = device;
+ data->label1 = label1;
+ data->label2 = label2;
+
+ /* Get some details about the device */
+ p = dbus_g_proxy_new_from_proxy (device, "org.freedesktop.DBus.Properties", NULL);
+ if (dbus_g_proxy_call (p, "GetAll", NULL, G_TYPE_STRING, "net.reactivated.Fprint.Device", G_TYPE_INVALID,
+ dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE), &props, G_TYPE_INVALID)) {
+ const char *scan_type;
+ data->name = g_value_dup_string (g_hash_table_lookup (props, "name"));
+ scan_type = g_value_dup_string (g_hash_table_lookup (props, "scan-type"));
+ if (g_str_equal (scan_type, "swipe"))
+ data->is_swipe = TRUE;
+ g_hash_table_destroy (props);
+ }
+ g_object_unref (p);
+
+ dialog = gtk_builder_new ();
+ filename = UIDIR "/account-fingerprint.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/account-fingerprint.ui";
+ if (!gtk_builder_add_from_file (dialog, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+ data->dialog = dialog;
+
+ ass = WID ("assistant");
+ gtk_window_set_title (GTK_WINDOW (ass), _("Enable Fingerprint Login"));
+ gtk_window_set_transient_for (GTK_WINDOW (ass), parent);
+ gtk_window_set_modal (GTK_WINDOW (ass), TRUE);
+ gtk_window_set_position (GTK_WINDOW (ass), GTK_WIN_POS_CENTER_ON_PARENT);
+
+ gtk_widget_realize (ass);
+ style = gtk_widget_get_style (ass);
+ gtk_widget_modify_fg (ass, GTK_STATE_SELECTED, &style->fg[GTK_STATE_NORMAL]);
+ gtk_widget_modify_bg (ass, GTK_STATE_SELECTED, &style->bg[GTK_STATE_NORMAL]);
+
+ g_signal_connect (G_OBJECT (ass), "cancel",
+ G_CALLBACK (assistant_cancelled), data);
+ g_signal_connect (G_OBJECT (ass), "close",
+ G_CALLBACK (assistant_cancelled), data);
+ g_signal_connect (G_OBJECT (ass), "prepare",
+ G_CALLBACK (assistant_prepare), data);
+
+ /* Page 1 */
+ gtk_combo_box_set_active (GTK_COMBO_BOX (WID ("finger_combobox")), 0);
+
+ g_signal_connect (G_OBJECT (WID ("radiobutton1")), "toggled",
+ G_CALLBACK (finger_radio_button_toggled), data);
+ g_signal_connect (G_OBJECT (WID ("radiobutton2")), "toggled",
+ G_CALLBACK (finger_radio_button_toggled), data);
+ g_signal_connect (G_OBJECT (WID ("radiobutton3")), "toggled",
+ G_CALLBACK (finger_radio_button_toggled), data);
+ g_signal_connect (G_OBJECT (WID ("finger_combobox")), "changed",
+ G_CALLBACK (finger_combobox_changed), data);
+
+ data->finger = selected_finger (dialog);
+
+ g_object_set_data (G_OBJECT (WID("page1")), "name", "intro");
+
+ /* translators:
+ * The variable is the name of the device, for example:
+ * "To enable fingerprint login, you need to save one of your fingerprints, using the
+ * 'Digital Persona U.are.U 4000/4000B' device." */
+ msg = g_strdup_printf (_("To enable fingerprint login, you need to save one of your fingerprints, using the '%s' device."),
+ data->name);
+ gtk_label_set_text (GTK_LABEL (WID("intro-label")), msg);
+ g_free (msg);
+
+ gtk_assistant_set_page_complete (GTK_ASSISTANT (ass), WID("page1"), TRUE);
+
+ pixbuf = um_user_render_icon (user, FALSE, 48);
+ title = g_strdup_printf (_("Enrolling fingerprints for\n%s"), um_user_get_real_name (user));
+
+ gtk_assistant_set_page_header_image (GTK_ASSISTANT (ass), WID("page1"), pixbuf);
+ gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page1"), title);
+ gtk_assistant_set_page_header_image (GTK_ASSISTANT (ass), WID("page2"), pixbuf);
+ gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page2"), title);
+ gtk_assistant_set_page_header_image (GTK_ASSISTANT (ass), WID("page3"), pixbuf);
+ gtk_assistant_set_page_title (GTK_ASSISTANT (ass), WID("page3"), title);
+ gtk_container_forall (GTK_CONTAINER (ass), align_image, NULL);
+ g_object_unref (pixbuf);
+ g_free (title);
+
+ /* Page 2 */
+ g_object_set_data (G_OBJECT (WID("page2")), "name", "enroll");
+
+ msg = g_strdup_printf (TR(finger_str_to_msg (data->finger, data->is_swipe)), data->name);
+ gtk_label_set_text (GTK_LABEL (WID("enroll-label")), msg);
+ g_free (msg);
+
+ /* Page 3 */
+ g_object_set_data (G_OBJECT (WID("page3")), "name", "summary");
+
+ data->ass = ass;
+ gtk_widget_show_all (ass);
+}
+
+void
+fingerprint_button_clicked (GtkWindow *parent,
+ GtkWidget *label1,
+ GtkWidget *label2,
+ UmUser *user)
+{
+ bindtextdomain ("fprintd", GNOMELOCALEDIR);
+ bind_textdomain_codeset ("fprintd", "UTF-8");
+
+ if (is_disable != FALSE) {
+ delete_fingerprints_question (parent, label1, label2, user);
+ } else {
+ enroll_fingerprints (parent, label1, label2, user);
+ }
+}
+
diff --git a/panels/user-accounts/um-fingerprint-dialog.h b/panels/user-accounts/um-fingerprint-dialog.h
new file mode 100644
index 000000000..cca4b5804
--- /dev/null
+++ b/panels/user-accounts/um-fingerprint-dialog.h
@@ -0,0 +1,28 @@
+/* gnome-about-me-fingerprint.h
+ * Copyright (C) 2008 Bastien Nocera
+ *
+ * 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, 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.
+ */
+
+#include
+#include "um-user.h"
+
+gboolean set_fingerprint_label (GtkWidget *label1,
+ GtkWidget *label2);
+void fingerprint_button_clicked (GtkWindow *parent,
+ GtkWidget *label1,
+ GtkWidget *label2,
+ UmUser *user);
diff --git a/panels/user-accounts/um-language-dialog.c b/panels/user-accounts/um-language-dialog.c
new file mode 100644
index 000000000..9362b6277
--- /dev/null
+++ b/panels/user-accounts/um-language-dialog.c
@@ -0,0 +1,555 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+
+#include "um-language-dialog.h"
+#include "um-user-manager.h"
+
+#include "gdm-languages.h"
+
+struct _UmLanguageDialog {
+ GtkWidget *dialog;
+ GtkWidget *user_icon;
+ GtkWidget *user_name;
+ GtkWidget *dialog_combo;
+ GtkListStore *dialog_store;
+
+ GtkWidget *chooser;
+ GtkWidget *chooser_list;
+ GtkListStore *chooser_store;
+
+ char *language;
+ UmUser *user;
+
+ gboolean force_setting;
+};
+
+enum {
+ LOCALE_COL,
+ DISPLAY_LOCALE_COL,
+ NUM_COLS
+};
+
+static void
+cancel_language_dialog (GtkButton *button,
+ UmLanguageDialog *um)
+{
+ if (um->force_setting)
+ um_user_set_language (um->user, um->language);
+ gtk_widget_hide (um->dialog);
+ um_language_dialog_set_user (um, NULL);
+
+}
+
+static void
+accept_language_dialog (GtkButton *button,
+ UmLanguageDialog *um)
+{
+ um_user_set_language (um->user, um->language);
+
+ gtk_widget_hide (um->dialog);
+ um_language_dialog_set_user (um, NULL);
+}
+
+gchar *
+um_language_chooser_get_language (GtkWidget *chooser)
+{
+ GtkTreeView *tv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *lang;
+
+ tv = (GtkTreeView *) g_object_get_data (G_OBJECT (chooser), "list");
+ selection = gtk_tree_view_get_selection (tv);
+ if (gtk_tree_selection_get_selected (selection, &model, &iter))
+ gtk_tree_model_get (model, &iter, LOCALE_COL, &lang, -1);
+ else
+ lang = NULL;
+
+ return lang;
+}
+
+static gint
+sort_languages (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer data)
+{
+ char *ca, *cb;
+ char *la, *lb;
+ gint result;
+
+ gtk_tree_model_get (model, a, LOCALE_COL, &ca, DISPLAY_LOCALE_COL, &la, -1);
+ gtk_tree_model_get (model, b, LOCALE_COL, &cb, DISPLAY_LOCALE_COL, &lb, -1);
+
+ if (!ca)
+ result = 1;
+ else if (!cb)
+ result = -1;
+ else
+ result = strcmp (la, lb);
+
+ g_free (ca);
+ g_free (cb);
+ g_free (la);
+ g_free (lb);
+
+ return result;
+}
+
+gboolean
+um_get_iter_for_language (GtkTreeModel *model,
+ const gchar *lang,
+ GtkTreeIter *iter)
+{
+ char *l;
+ char *name;
+ char *language;
+
+ gtk_tree_model_get_iter_first (model, iter);
+ do {
+ gtk_tree_model_get (model, iter, LOCALE_COL, &l, -1);
+ if (g_strcmp0 (l, lang) == 0) {
+ g_free (l);
+ return TRUE;
+ }
+ g_free (l);
+ } while (gtk_tree_model_iter_next (model, iter));
+
+ name = gdm_normalize_language_name (lang);
+ if (name != NULL) {
+ language = gdm_get_language_from_name (name, NULL);
+
+ gtk_list_store_append (GTK_LIST_STORE (model), iter);
+ gtk_list_store_set (GTK_LIST_STORE (model), iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, language, -1);
+ g_free (name);
+ g_free (language);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+select_language (UmLanguageDialog *um,
+ const gchar *lang)
+{
+ if (um->chooser)
+ gtk_widget_hide (um->chooser);
+}
+
+static void
+row_activated (GtkTreeView *tree_view,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ GtkWidget *chooser)
+{
+ gtk_dialog_response (GTK_DIALOG (chooser), GTK_RESPONSE_OK);
+}
+
+static gboolean
+language_has_font (const gchar *locale)
+{
+ const FcCharSet *charset;
+ FcPattern *pattern;
+ FcObjectSet *object_set;
+ FcFontSet *font_set;
+ gchar *language_code;
+ gboolean is_displayable;
+
+ is_displayable = FALSE;
+ pattern = NULL;
+ object_set = NULL;
+ font_set = NULL;
+
+ if (!gdm_parse_language_name (locale, &language_code, NULL, NULL, NULL))
+ return FALSE;
+
+ charset = FcLangGetCharSet ((FcChar8 *) language_code);
+ if (!charset) {
+ /* fontconfig does not know about this language */
+ is_displayable = TRUE;
+ }
+ else {
+ /* see if any fonts support rendering it */
+ pattern = FcPatternBuild (NULL, FC_LANG, FcTypeString, language_code, NULL);
+
+ if (pattern == NULL)
+ goto done;
+
+ object_set = FcObjectSetCreate ();
+
+ if (object_set == NULL)
+ goto done;
+
+ font_set = FcFontList (NULL, pattern, object_set);
+
+ if (font_set == NULL)
+ goto done;
+
+ is_displayable = (font_set->nfont > 0);
+ }
+
+ done:
+ if (font_set != NULL)
+ FcFontSetDestroy (font_set);
+
+ if (object_set != NULL)
+ FcObjectSetDestroy (object_set);
+
+ if (pattern != NULL)
+ FcPatternDestroy (pattern);
+
+ g_free (language_code);
+
+ return is_displayable;
+}
+
+static void
+add_available_languages (GtkListStore *store)
+{
+ char **languages;
+ int i;
+ char *name;
+ char *language;
+ GtkTreeIter iter;
+
+ gtk_list_store_clear (store);
+
+ languages = gdm_get_all_language_names ();
+
+ for (i = 0; languages[i] != NULL; i++) {
+ if (!language_has_font (languages[i]))
+ continue;
+
+ name = gdm_normalize_language_name (languages[i]);
+ language = gdm_get_language_from_name (name, NULL);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, language, -1);
+
+ g_free (name);
+ g_free (language);
+ }
+
+ g_strfreev (languages);
+}
+
+void
+um_add_user_languages (GtkTreeModel *model)
+{
+ GHashTable *seen;
+ GSList *users, *l;
+ UmUser *user;
+ const char *lang;
+ char *name;
+ char *language;
+ GtkTreeIter iter;
+ UmUserManager *manager;
+ GtkListStore *store = GTK_LIST_STORE (model);
+
+ gtk_list_store_clear (store);
+
+ seen = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+ manager = um_user_manager_ref_default ();
+ users = um_user_manager_list_users (manager);
+ g_object_unref (manager);
+
+ for (l = users; l; l = l->next) {
+ user = l->data;
+ lang = um_user_get_language (user);
+ if (!lang || !language_has_font (lang)) {
+ continue;
+ }
+
+ name = gdm_normalize_language_name (lang);
+
+ if (g_hash_table_lookup (seen, name)) {
+ g_free (name);
+ continue;
+ }
+
+ g_hash_table_insert (seen, name, GINT_TO_POINTER (TRUE));
+
+ language = gdm_get_language_from_name (name, NULL);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, language, -1);
+
+ g_free (language);
+ }
+
+ g_slist_free (users);
+
+ /* Make sure the current locale is present */
+ name = um_get_current_language ();
+
+ if (!g_hash_table_lookup (seen, name)) {
+ language = gdm_get_language_from_name (name, NULL);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, language, -1);
+ g_free (language);
+ }
+
+ g_free (name);
+
+ g_hash_table_destroy (seen);
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter, LOCALE_COL, NULL, DISPLAY_LOCALE_COL, _("Other..."), -1);
+}
+
+gchar *
+um_get_current_language (void)
+{
+ gchar *language;
+ const gchar *locale;
+
+ locale = (const gchar *) setlocale (LC_MESSAGES, NULL);
+ if (locale)
+ language = gdm_normalize_language_name (locale);
+ else
+ language = NULL;
+
+ return language;
+}
+
+GtkWidget *
+um_language_chooser_new (void)
+{
+ GtkBuilder *builder;
+ const char *filename;
+ GError *error = NULL;
+ GtkWidget *chooser;
+ GtkWidget *list;
+ GtkWidget *button;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GtkListStore *store;
+
+ builder = gtk_builder_new ();
+ filename = UIDIR "/language-chooser.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/language-chooser.ui";
+ if (!gtk_builder_add_from_file (builder, filename, &error)) {
+ g_warning ("failed to load language chooser: %s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ chooser = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+
+ list = (GtkWidget *) gtk_builder_get_object (builder, "language-list");
+ g_object_set_data (G_OBJECT (chooser), "list", list);
+ g_signal_connect (list, "row-activated",
+ G_CALLBACK (row_activated), chooser);
+
+ button = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
+ button = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ gtk_widget_grab_default (button);
+
+ cell = gtk_cell_renderer_text_new ();
+ column = gtk_tree_view_column_new_with_attributes (NULL, cell, "text", DISPLAY_LOCALE_COL, NULL);
+ gtk_tree_view_append_column (GTK_TREE_VIEW (list), column);
+ store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+ sort_languages, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+
+ gtk_tree_view_set_model (GTK_TREE_VIEW (list), GTK_TREE_MODEL (store));
+
+ add_available_languages (store);
+
+ g_object_unref (builder);
+
+ return chooser;
+}
+
+static void
+language_combo_changed (GtkComboBox *combo,
+ UmLanguageDialog *um)
+{
+ GtkTreeIter iter;
+ char *lang;
+
+ if (!gtk_combo_box_get_active_iter (combo, &iter))
+ return;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (um->dialog_store), &iter, LOCALE_COL, &lang, -1);
+
+ if (lang) {
+ g_free (um->language);
+ um->language = lang;
+ return;
+ }
+
+#if 0
+ if (!um->chooser)
+ setup_language_chooser (um);
+#endif
+
+ gtk_window_present (GTK_WINDOW (um->chooser));
+ gtk_widget_grab_focus (um->chooser_list);
+
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
+}
+
+UmLanguageDialog *
+um_language_dialog_new (void)
+{
+ GtkBuilder *builder;
+ GtkWidget *widget;
+ UmLanguageDialog *um;
+ const gchar *filename;
+ GtkListStore *store;
+ GError *error = NULL;
+
+ builder = gtk_builder_new ();
+
+ filename = UIDIR "/language-dialog.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/language-dialog.ui";
+ if (!gtk_builder_add_from_file (builder, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ um = g_new0 (UmLanguageDialog, 1);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+ g_signal_connect (widget, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+ um->dialog = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (cancel_language_dialog), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (accept_language_dialog), um);
+ gtk_widget_grab_default (widget);
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+ sort_languages, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+ um->dialog_store = store;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "language-combobox");
+ gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (store));
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (language_combo_changed), um);
+ um->dialog_combo = widget;
+
+ um->user_icon = (GtkWidget *) gtk_builder_get_object (builder, "user-icon");
+ um->user_name = (GtkWidget *) gtk_builder_get_object (builder, "user-name");
+
+ return um;
+}
+
+void
+um_language_dialog_free (UmLanguageDialog *um)
+{
+ gtk_widget_destroy (um->dialog);
+
+ if (um->chooser)
+ gtk_widget_destroy (um->chooser);
+
+ g_free (um->language);
+
+ if (um->user)
+ g_object_unref (um->user);
+
+ g_free (um);
+}
+
+void
+um_language_dialog_set_user (UmLanguageDialog *um,
+ UmUser *user)
+{
+ GdkPixbuf *pixbuf;
+ const gchar *name;
+
+ if (um->user) {
+ g_object_unref (um->user);
+ um->user = NULL;
+ }
+ if (um->language) {
+ g_free (um->language);
+ um->language = NULL;
+ }
+ um->force_setting = FALSE;
+
+ um->user = user;
+ if (um->user) {
+ const gchar *language;
+
+ g_object_ref (user);
+
+ pixbuf = um_user_render_icon (user, FALSE, 48);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (um->user_icon), pixbuf);
+ g_object_unref (pixbuf);
+
+ name = um_user_get_real_name (user);
+ gtk_label_set_label (GTK_LABEL (um->user_name), name);
+
+ um_add_user_languages (gtk_combo_box_get_model (GTK_COMBO_BOX (um->dialog_combo)));
+
+ language = um_user_get_language (user);
+ if (language) {
+ select_language (um, language);
+ } else if (um_user_get_uid (user) == getuid ()) {
+ gchar *lang;
+
+ lang = um_get_current_language ();
+ select_language (um, lang);
+ g_free (lang);
+ um->force_setting = TRUE;
+ }
+ }
+}
+
+void
+um_language_dialog_show (UmLanguageDialog *um,
+ GtkWindow *parent)
+{
+ gtk_window_set_transient_for (GTK_WINDOW (um->dialog), parent);
+ gtk_window_present (GTK_WINDOW (um->dialog));
+ gtk_widget_grab_focus (um->dialog_combo);
+}
+
diff --git a/panels/user-accounts/um-language-dialog.h b/panels/user-accounts/um-language-dialog.h
new file mode 100644
index 000000000..5df8af397
--- /dev/null
+++ b/panels/user-accounts/um-language-dialog.h
@@ -0,0 +1,49 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef __UM_LANGUAGE_DIALOG_H__
+#define __UM_LANGUAGE_DIALOG_H__
+
+#include
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+typedef struct _UmLanguageDialog UmLanguageDialog;
+
+UmLanguageDialog *um_language_dialog_new (void);
+void um_language_dialog_free (UmLanguageDialog *dialog);
+void um_language_dialog_set_user (UmLanguageDialog *dialog,
+ UmUser *user);
+void um_language_dialog_show (UmLanguageDialog *dialog,
+ GtkWindow *parent);
+void um_add_user_languages (GtkTreeModel *model);
+gchar *um_get_current_language (void);
+gboolean um_get_iter_for_language (GtkTreeModel *model,
+ const gchar *lang,
+ GtkTreeIter *iter);
+
+GtkWidget *um_language_chooser_new (void);
+gchar *um_language_chooser_get_language (GtkWidget *chooser);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-lockbutton.c b/panels/user-accounts/um-lockbutton.c
new file mode 100644
index 000000000..88da58cad
--- /dev/null
+++ b/panels/user-accounts/um-lockbutton.c
@@ -0,0 +1,637 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Author: Matthias Clasen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "um-lockbutton.h"
+
+#include
+#include
+
+#define P_(s) s
+
+struct _UmLockButtonPrivate
+{
+ GPermission *permission;
+
+ gchar *text_lock;
+ gchar *text_unlock;
+ gchar *text_not_authorized;
+
+ gchar *tooltip_lock;
+ gchar *tooltip_unlock;
+ gchar *tooltip_not_authorized;
+
+ GtkWidget *box;
+ GtkWidget *eventbox;
+ GtkWidget *image;
+ GtkWidget *button;
+ GtkWidget *notebook;
+
+ GtkWidget *label_lock;
+ GtkWidget *label_unlock;
+ GtkWidget *label_not_authorized;
+
+ GCancellable *cancellable;
+
+ gboolean constructed;
+};
+
+enum
+{
+ PROP_0,
+ PROP_PERMISSION,
+ PROP_TEXT_LOCK,
+ PROP_TEXT_UNLOCK,
+ PROP_TEXT_NOT_AUTHORIZED,
+ PROP_TOOLTIP_LOCK,
+ PROP_TOOLTIP_UNLOCK,
+ PROP_TOOLTIP_NOT_AUTHORIZED
+};
+
+enum
+{
+ CHANGED_SIGNAL,
+ LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = {0, };
+
+static void initiate_check (UmLockButton *button);
+static void do_sync_check (UmLockButton *button);
+static void update_state (UmLockButton *button);
+
+static void on_permission_changed (GPermission *permission,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+static void on_clicked (GtkButton *button,
+ gpointer user_data);
+
+static void on_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data);
+
+G_DEFINE_TYPE (UmLockButton, um_lock_button, GTK_TYPE_BIN);
+
+static void
+um_lock_button_finalize (GObject *object)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (object);
+ UmLockButtonPrivate *priv = button->priv;
+
+ g_free (priv->text_lock);
+ g_free (priv->text_unlock);
+ g_free (priv->text_not_authorized);
+
+ g_free (priv->tooltip_lock);
+ g_free (priv->tooltip_unlock);
+ g_free (priv->tooltip_not_authorized);
+
+ if (priv->cancellable != NULL)
+ {
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ }
+
+ g_signal_handlers_disconnect_by_func (priv->permission,
+ on_permission_changed,
+ button);
+
+ g_object_unref (priv->permission);
+
+ G_OBJECT_CLASS (um_lock_button_parent_class)->finalize (object);
+}
+
+static void
+um_lock_button_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (object);
+ UmLockButtonPrivate *priv = button->priv;
+
+ switch (property_id)
+ {
+ case PROP_PERMISSION:
+ g_value_set_object (value, priv->permission);
+ break;
+
+ case PROP_TEXT_LOCK:
+ g_value_set_string (value, priv->text_lock);
+ break;
+
+ case PROP_TEXT_UNLOCK:
+ g_value_set_string (value, priv->text_unlock);
+ break;
+
+ case PROP_TEXT_NOT_AUTHORIZED:
+ g_value_set_string (value, priv->text_not_authorized);
+ break;
+
+ case PROP_TOOLTIP_LOCK:
+ g_value_set_string (value, priv->tooltip_lock);
+ break;
+
+ case PROP_TOOLTIP_UNLOCK:
+ g_value_set_string (value, priv->tooltip_unlock);
+ break;
+
+ case PROP_TOOLTIP_NOT_AUTHORIZED:
+ g_value_set_string (value, priv->tooltip_not_authorized);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+um_lock_button_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (object);
+ UmLockButtonPrivate *priv = button->priv;
+
+ switch (property_id)
+ {
+ case PROP_PERMISSION:
+ priv->permission = g_value_get_object (value);
+ break;
+
+ case PROP_TEXT_LOCK:
+ g_free (priv->text_lock);
+ priv->text_lock = g_value_dup_string (value);
+ break;
+
+ case PROP_TEXT_UNLOCK:
+ g_free (priv->text_unlock);
+ priv->text_unlock = g_value_dup_string (value);
+ break;
+
+ case PROP_TEXT_NOT_AUTHORIZED:
+ g_free (priv->text_not_authorized);
+ priv->text_not_authorized = g_value_dup_string (value);
+ break;
+
+ case PROP_TOOLTIP_LOCK:
+ g_free (priv->tooltip_lock);
+ priv->tooltip_lock = g_value_dup_string (value);
+ break;
+
+ case PROP_TOOLTIP_UNLOCK:
+ g_free (priv->tooltip_unlock);
+ priv->tooltip_unlock = g_value_dup_string (value);
+ break;
+
+ case PROP_TOOLTIP_NOT_AUTHORIZED:
+ g_free (priv->tooltip_not_authorized);
+ priv->tooltip_not_authorized = g_value_dup_string (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+
+ if (priv->constructed)
+ update_state (button);
+}
+
+static void
+um_lock_button_init (UmLockButton *button)
+{
+ button->priv = G_TYPE_INSTANCE_GET_PRIVATE (button,
+ UM_TYPE_LOCK_BUTTON,
+ UmLockButtonPrivate);
+}
+
+static void
+um_lock_button_constructed (GObject *object)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (object);
+ UmLockButtonPrivate *priv = button->priv;
+
+ priv->constructed = TRUE;
+
+ g_signal_connect (priv->permission, "notify",
+ G_CALLBACK (on_permission_changed), button);
+
+ priv->box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, FALSE, 6);
+ gtk_container_add (GTK_CONTAINER (button), priv->box);
+
+ priv->eventbox = gtk_event_box_new ();
+ gtk_event_box_set_visible_window (GTK_EVENT_BOX (priv->eventbox), FALSE);
+ gtk_container_add (GTK_CONTAINER (priv->box), priv->eventbox);
+ gtk_widget_show (priv->eventbox);
+
+ priv->image = gtk_image_new (); /* image is set in update_state() */
+ gtk_container_add (GTK_CONTAINER (priv->eventbox), priv->image);
+ gtk_widget_show (priv->image);
+
+ priv->notebook = gtk_notebook_new ();
+ gtk_notebook_set_show_tabs (GTK_NOTEBOOK (priv->notebook), FALSE);
+ gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), FALSE);
+ gtk_widget_show (priv->notebook);
+
+ priv->button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (priv->button), priv->notebook);
+ gtk_widget_show (priv->button);
+
+ priv->label_lock = gtk_label_new (""); /* text is set in update_state */
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_lock, NULL);
+ gtk_widget_show (priv->label_lock);
+
+ priv->label_unlock = gtk_label_new ("");
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_unlock, NULL);
+ gtk_widget_show (priv->label_unlock);
+
+ priv->label_not_authorized = gtk_label_new ("");
+ gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), priv->label_not_authorized, NULL);
+ gtk_widget_show (priv->label_not_authorized);
+
+ gtk_box_pack_start (GTK_BOX (priv->box), priv->button, FALSE, FALSE, 0);
+ gtk_widget_show (priv->button);
+
+ g_signal_connect (priv->eventbox, "button-press-event",
+ G_CALLBACK (on_button_press), button);
+ g_signal_connect (priv->button, "clicked",
+ G_CALLBACK (on_clicked), button);
+
+ gtk_widget_set_no_show_all (priv->box, TRUE);
+
+ update_state (button);
+
+ if (G_OBJECT_CLASS (um_lock_button_parent_class)->constructed != NULL)
+ G_OBJECT_CLASS (um_lock_button_parent_class)->constructed (object);
+}
+
+static void
+um_lock_button_size_request (GtkWidget *widget,
+ GtkRequisition *requisition)
+{
+ UmLockButtonPrivate *priv = UM_LOCK_BUTTON (widget)->priv;
+
+ gtk_widget_get_preferred_size (priv->box, requisition, NULL);
+}
+
+static void
+um_lock_button_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation)
+{
+ UmLockButtonPrivate *priv = UM_LOCK_BUTTON (widget)->priv;
+ GtkRequisition requisition;
+ GtkAllocation child_allocation;
+
+ gtk_widget_set_allocation (widget, allocation);
+ gtk_widget_get_preferred_size (priv->box, &requisition, NULL);
+ child_allocation.x = allocation->x;
+ child_allocation.y = allocation->y;
+ child_allocation.width = requisition.width;
+ child_allocation.height = requisition.height;
+ gtk_widget_size_allocate (priv->box, &child_allocation);
+}
+
+static void
+um_lock_button_class_init (UmLockButtonClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ gobject_class->finalize = um_lock_button_finalize;
+ gobject_class->get_property = um_lock_button_get_property;
+ gobject_class->set_property = um_lock_button_set_property;
+ gobject_class->constructed = um_lock_button_constructed;
+
+ widget_class->size_request = um_lock_button_size_request;
+ widget_class->size_allocate = um_lock_button_size_allocate;
+
+ g_type_class_add_private (klass, sizeof (UmLockButtonPrivate));
+
+ g_object_class_install_property (gobject_class, PROP_PERMISSION,
+ g_param_spec_object ("permission",
+ P_("Permission"),
+ P_("The GPermission object controlling this button"),
+ G_TYPE_PERMISSION,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TEXT_LOCK,
+ g_param_spec_string ("text-lock",
+ P_("Lock Text"),
+ P_("The text to display when prompting the user to lock"),
+ _("Lock"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TEXT_UNLOCK,
+ g_param_spec_string ("text-unlock",
+ P_("Unlock Text"),
+ P_("The text to display when prompting the user to unlock"),
+ _("Unlock"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TEXT_NOT_AUTHORIZED,
+ g_param_spec_string ("text-not-authorized",
+ P_("Not Authorized Text"),
+ P_("The text to display when prompting the user cannot obtain authorization"),
+ _("Locked"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TOOLTIP_LOCK,
+ g_param_spec_string ("tooltip-lock",
+ P_("Lock Tooltip"),
+ P_("The tooltip to display when prompting the user to lock"),
+ _("Dialog is unlocked.\nClick to prevent further changes"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TOOLTIP_UNLOCK,
+ g_param_spec_string ("tooltip-unlock",
+ P_("Unlock Tooltip"),
+ P_("The tooltip to display when prompting the user to unlock"),
+ _("Dialog is locked.\nClick to make changes"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class, PROP_TOOLTIP_NOT_AUTHORIZED,
+ g_param_spec_string ("tooltip-not-authorized",
+ P_("Not Authorized Tooltip"),
+ P_("The tooltip to display when prompting the user cannot obtain authorization"),
+ _("System policy prevents changes.\nContact your system administrator"),
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT |
+ G_PARAM_STATIC_STRINGS));
+
+ signals[CHANGED_SIGNAL] = g_signal_new ("changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmLockButtonClass, changed),
+ NULL,
+ NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE,
+ 0);
+}
+
+/**
+ * um_lock_button_new:
+ * @permission: a #GPermission
+ *
+ * Creates a new lock button which reflects the @permission.
+ *
+ * Returns: a new #UmLockButton
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+um_lock_button_new (GPermission *permission)
+{
+ g_return_val_if_fail (permission != NULL, NULL);
+
+ return GTK_WIDGET (g_object_new (UM_TYPE_LOCK_BUTTON,
+ "permission", permission,
+ NULL));
+}
+
+static void
+update_state (UmLockButton *button)
+{
+ UmLockButtonPrivate *priv = button->priv;
+ gint page;
+ const gchar *tooltip;
+ gboolean sensitive;
+ gboolean visible;
+ GIcon *icon;
+
+ visible = TRUE;
+ sensitive = TRUE;
+
+ gtk_label_set_text (GTK_LABEL (priv->label_lock), priv->text_lock);
+ gtk_label_set_text (GTK_LABEL (priv->label_unlock), priv->text_unlock);
+ gtk_label_set_text (GTK_LABEL (priv->label_not_authorized), priv->text_not_authorized);
+
+ if (g_permission_get_allowed (priv->permission))
+ {
+ if (g_permission_get_can_release (priv->permission))
+ {
+ page = 0;
+ tooltip = priv->tooltip_lock;
+ sensitive = TRUE;
+ }
+ else
+ {
+ page = 0;
+ tooltip = "";
+ visible = FALSE;
+ }
+ }
+ else
+ {
+ if (g_permission_get_can_acquire (priv->permission))
+ {
+ page = 1;
+ tooltip = button->priv->tooltip_unlock;
+ sensitive = TRUE;
+ }
+ else
+ {
+ page = 2;
+ tooltip = button->priv->tooltip_not_authorized;
+ sensitive = FALSE;
+ }
+ }
+
+ if (g_permission_get_allowed (priv->permission))
+ {
+ gchar *names[3];
+
+ names[0] = "changes-allow-symbolic";
+ names[1] = "changes-allow";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ }
+ else
+ {
+ gchar *names[3];
+
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ }
+
+ gtk_image_set_from_gicon (GTK_IMAGE (priv->image), icon, GTK_ICON_SIZE_BUTTON);
+ g_object_unref (icon);
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), page);
+ gtk_widget_set_tooltip_markup (priv->box, tooltip);
+
+ gtk_widget_set_sensitive (priv->box, sensitive);
+
+ if (visible)
+ gtk_widget_show (priv->box);
+ else
+ gtk_widget_hide (priv->box);
+}
+
+static void
+on_permission_changed (GPermission *permission,
+ GParamSpec *pspec,
+ gpointer user_data)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (user_data);
+
+ update_state (button);
+}
+
+static void
+acquire_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (user_data);
+ UmLockButtonPrivate *priv = button->priv;
+ GError *error;
+ gboolean res;
+
+ error = NULL;
+ res = g_permission_acquire_finish (priv->permission, result, &error);
+
+ if (error)
+ {
+ g_warning ("Error acquiring permission: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+
+ update_state (button);
+}
+
+static void
+release_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ UmLockButton *button = UM_LOCK_BUTTON (user_data);
+ UmLockButtonPrivate *priv = button->priv;
+ GError *error;
+ gboolean res;
+
+ error = NULL;
+ res = g_permission_release_finish (priv->permission, result, &error);
+
+ if (error)
+ {
+ g_warning ("Error releasing permission: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_object_unref (priv->cancellable);
+ priv->cancellable = NULL;
+
+ update_state (button);
+}
+
+static void
+handle_click (UmLockButton *button)
+{
+ UmLockButtonPrivate *priv = button->priv;
+
+ if (!g_permission_get_allowed (priv->permission) &&
+ g_permission_get_can_acquire (priv->permission))
+ {
+ /* if we already have a pending interactive check, then do nothing */
+ if (priv->cancellable != NULL)
+ goto out;
+
+ priv->cancellable = g_cancellable_new ();
+
+ g_permission_acquire_async (priv->permission,
+ priv->cancellable,
+ acquire_cb,
+ button);
+ }
+ else if (g_permission_get_allowed (priv->permission) &&
+ g_permission_get_can_release (priv->permission))
+ {
+ /* if we already have a pending interactive check, then do nothing */
+ if (priv->cancellable != NULL)
+ goto out;
+
+ priv->cancellable = g_cancellable_new ();
+
+ g_permission_release_async (priv->permission,
+ priv->cancellable,
+ release_cb,
+ button);
+ }
+
+ out: ;
+}
+
+static void
+on_clicked (GtkButton *_button,
+ gpointer user_data)
+
+{
+ handle_click (UM_LOCK_BUTTON (user_data));
+}
+
+static void
+on_button_press (GtkWidget *widget,
+ GdkEventButton *event,
+ gpointer user_data)
+{
+ handle_click (UM_LOCK_BUTTON (user_data));
+}
+
+/**
+ * um_lock_button_get_permission:
+ * @button: a #UmLockButton
+ *
+ * Obtains the #GPermission object that controls @button.
+ *
+ * Returns: the #GPermission of @button
+ *
+ * Since: 3.0
+ */
+GPermission *
+um_lock_button_get_permission (UmLockButton *button)
+{
+ g_return_val_if_fail (UM_IS_LOCK_BUTTON (button), NULL);
+
+ return button->priv->permission;
+}
+
diff --git a/panels/user-accounts/um-lockbutton.h b/panels/user-accounts/um-lockbutton.h
new file mode 100644
index 000000000..6becfbc12
--- /dev/null
+++ b/panels/user-accounts/um-lockbutton.h
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Author: Matthias Clasen
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __UM_LOCK_BUTTON_H__
+#define __UM_LOCK_BUTTON_H__
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_LOCK_BUTTON (um_lock_button_get_type ())
+#define UM_LOCK_BUTTON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), UM_TYPE_LOCK_BUTTON, UmLockButton))
+#define UM_LOCK_BUTTON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), UM_LOCK_BUTTON, UmLockButtonClass))
+#define UM_IS_LOCK_BUTTON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), UM_TYPE_LOCK_BUTTON))
+#define UM_IS_LOCK_BUTTON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), UM_TYPE_LOCK_BUTTON))
+#define UM_LOCK_BUTTON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), UM_TYPE_LOCK_BUTTON, UmLockButtonClass))
+
+typedef struct _UmLockButton UmLockButton;
+typedef struct _UmLockButtonClass UmLockButtonClass;
+typedef struct _UmLockButtonPrivate UmLockButtonPrivate;
+
+struct _UmLockButton
+{
+ GtkBin parent;
+
+ UmLockButtonPrivate *priv;
+};
+
+struct _UmLockButtonClass
+{
+ GtkBinClass parent_class;
+
+ void (*changed) (UmLockButton *button);
+
+ void (*reserved0) (void);
+ void (*reserved1) (void);
+ void (*reserved2) (void);
+ void (*reserved3) (void);
+ void (*reserved4) (void);
+ void (*reserved5) (void);
+ void (*reserved6) (void);
+ void (*reserved7) (void);
+};
+
+GType um_lock_button_get_type (void) G_GNUC_CONST;
+GtkWidget *um_lock_button_new (GPermission *permission);
+GPermission *um_lock_button_get_permission (UmLockButton *button);
+
+
+G_END_DECLS
+
+#endif /* __UM_LOCK_BUTTON_H__ */
diff --git a/panels/user-accounts/um-login-options.c b/panels/user-accounts/um-login-options.c
new file mode 100644
index 000000000..99b57ce53
--- /dev/null
+++ b/panels/user-accounts/um-login-options.c
@@ -0,0 +1,433 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include "um-login-options.h"
+#include "um-lockbutton.h"
+#include "um-user-manager.h"
+#include "um-user.h"
+
+struct _UmLoginOptions {
+ GtkWidget *autologin_combo;
+ GtkWidget *userlist_check;
+ GtkWidget *power_check;
+ GtkWidget *hints_check;
+ GtkWidget *guest_check;
+ GtkWidget *lock_button;
+ PolkitPermission *permission;
+
+ UmUserManager *manager;
+
+ DBusGProxy *proxy;
+ DBusGConnection *connection;
+};
+
+enum {
+ AUTOLOGIN_NAME_COL,
+ AUTOLOGIN_USER_COL,
+ NUM_AUTOLOGIN_COLS
+};
+
+static gint
+sort_login_users (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer data)
+{
+ UmUser *ua, *ub;
+ gint result;
+
+ gtk_tree_model_get (model, a, AUTOLOGIN_USER_COL, &ua, -1);
+ gtk_tree_model_get (model, b, AUTOLOGIN_USER_COL, &ub, -1);
+
+ if (ua == NULL)
+ result = -1;
+ else if (ub == NULL)
+ result = 1;
+ else if (um_user_get_uid (ua) == getuid ())
+ result = -1;
+ else if (um_user_get_uid (ub) == getuid ())
+ result = 1;
+ else
+ result = um_user_collate (ua, ub);
+
+ if (ua)
+ g_object_unref (ua);
+
+ if (ub)
+ g_object_unref (ub);
+
+ return result;
+}
+
+static void
+user_added (UmUserManager *um, UmUser *user, UmLoginOptions *d)
+{
+ GtkComboBox *combo;
+ GtkListStore *store;
+ GtkTreeIter iter;
+
+ g_debug ("adding user '%s', %d", um_user_get_user_name (user), um_user_get_automatic_login (user));
+ combo = GTK_COMBO_BOX (d->autologin_combo);
+ store = (GtkListStore*)gtk_combo_box_get_model (combo);
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ AUTOLOGIN_NAME_COL, um_user_get_display_name (user),
+ AUTOLOGIN_USER_COL, user,
+ -1);
+
+ if (um_user_get_automatic_login (user)) {
+ gtk_combo_box_set_active_iter (combo, &iter);
+ }
+}
+
+static void
+user_removed (UmUserManager *um, UmUser *user, UmLoginOptions *d)
+{
+ GtkComboBox *combo;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ UmUser *u;
+
+ combo = GTK_COMBO_BOX (d->autologin_combo);
+ model = gtk_combo_box_get_model (combo);
+ store = (GtkListStore*)model;
+
+ gtk_combo_box_get_active_iter (combo, &iter);
+ gtk_tree_model_get (model, &iter, AUTOLOGIN_USER_COL, &u, -1);
+ if (u != NULL) {
+ if (um_user_get_uid (user) == um_user_get_uid (u)) {
+ /* autologin user got removed, set back to Disabled */
+ gtk_list_store_remove (store, &iter);
+ gtk_combo_box_set_active (combo, 0);
+ g_object_unref (u);
+ return;
+ }
+ g_object_unref (u);
+ }
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ do {
+ gtk_tree_model_get (model, &iter, AUTOLOGIN_USER_COL, &u, -1);
+
+ if (u != NULL) {
+ if (um_user_get_uid (user) == um_user_get_uid (u)) {
+ gtk_list_store_remove (store, &iter);
+ g_object_unref (u);
+ return;
+ }
+ g_object_unref (u);
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+}
+
+static void
+user_changed (UmUserManager *manager,
+ UmUser *user,
+ UmLoginOptions *d)
+{
+ /* FIXME */
+}
+
+static void
+users_loaded (UmUserManager *manager,
+ UmLoginOptions *d)
+{
+ GSList *list, *l;
+ UmUser *user;
+
+ list = um_user_manager_list_users (manager);
+ for (l = list; l; l = l->next) {
+ user = l->data;
+ user_added (manager, user, d);
+ }
+ g_slist_free (list);
+
+ g_signal_connect (manager, "user-added", G_CALLBACK (user_added), d);
+ g_signal_connect (manager, "user-removed", G_CALLBACK (user_removed), d);
+ g_signal_connect (manager, "user-changed", G_CALLBACK (user_changed), d);
+}
+
+static void update_login_options (GtkWidget *widget, UmLoginOptions *d);
+
+static void
+update_boolean_from_gconf (GtkWidget *widget,
+ UmLoginOptions *d)
+{
+ gchar *cmdline;
+ gboolean value;
+ gchar *std_out;
+ gchar *std_err;
+ gint status;
+ GError *error;
+ const gchar *key;
+
+ key = g_object_get_data (G_OBJECT (widget), "gconf-key");
+
+ /* GConf fail.
+ * gconfd does not pick up any changes in the default or mandatory
+ * databases at runtime. Even a SIGHUP doesn't seem to help. So we
+ * have to use gconftool to go get the current mandatory values.
+ */
+ cmdline = g_strdup_printf ("gconftool-2 --direct --config-source=\"xml:readonly:/etc/gconf/gconf.xml.defaults;xml:readonly:/etc/gconf/gconf.xml.mandatory\" --get %s", key);
+
+ error = NULL;
+ std_out = NULL;
+ std_err = NULL;
+ if (!g_spawn_command_line_sync (cmdline, &std_out, &std_err, &status, &error)) {
+ g_warning ("Failed to run '%s': %s", cmdline, error->message);
+ g_error_free (error);
+ g_free (cmdline);
+ g_free (std_out);
+ g_free (std_err);
+ return;
+ }
+ if (WEXITSTATUS (status) != 0) {
+ g_warning ("Failed to run '%s': %s", cmdline, std_err);
+ g_free (cmdline);
+ g_free (std_out);
+ g_free (std_err);
+ return;
+ }
+
+ if (std_out[strlen (std_out) - 1] == '\n') {
+ std_out[strlen (std_out) - 1] = 0;
+ }
+
+ if (g_strcmp0 (std_out, "true") == 0) {
+ value = TRUE;
+ }
+ else {
+ value = FALSE;
+ }
+ g_signal_handlers_block_by_func (widget, update_login_options, d);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), !value);
+ g_signal_handlers_unblock_by_func (widget, update_login_options, d);
+
+ g_free (cmdline);
+ g_free (std_out);
+ g_free (std_err);
+}
+
+static void
+update_login_options (GtkWidget *widget,
+ UmLoginOptions *d)
+{
+ GError *error;
+ gboolean active;
+ GConfValue *value;
+ const gchar *key = NULL;
+ gchar *value_string;
+
+ if (widget == d->userlist_check ||
+ widget == d->power_check) {
+ active = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
+ key = g_object_get_data (G_OBJECT (widget), "gconf-key");
+ }
+ else {
+ g_warning ("unhandled option in update_login_options");
+ return;
+ }
+
+ error = NULL;
+ value = gconf_value_new (GCONF_VALUE_BOOL);
+ gconf_value_set_bool (value, !active);
+ value_string = gconf_value_encode (value);
+ if (!dbus_g_proxy_call (d->proxy, "SetMandatoryValue",
+ &error,
+ G_TYPE_STRING, key,
+ G_TYPE_STRING, value_string,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("error calling SetMandatoryValue: %s\n", error->message);
+ g_error_free (error);
+ }
+ g_free (value_string);
+ gconf_value_free (value);
+ update_boolean_from_gconf (widget, d);
+}
+
+static void
+update_autologin (GtkWidget *widget,
+ UmLoginOptions *d)
+{
+ GtkComboBox *combo = GTK_COMBO_BOX (widget);
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ UmUser *user;
+ gboolean enabled;
+
+ if (!gtk_widget_is_sensitive (widget))
+ return;
+
+ model = gtk_combo_box_get_model (combo);
+ gtk_combo_box_get_active_iter (combo, &iter);
+ gtk_tree_model_get (model, &iter, AUTOLOGIN_USER_COL, &user, -1);
+ if (user) {
+ enabled = TRUE;
+ }
+ else {
+ enabled = FALSE;
+ user = um_user_manager_get_user_by_id (d->manager, getuid ());
+ g_object_ref (user);
+ }
+
+ um_user_set_automatic_login (user, enabled);
+
+ g_object_unref (user);
+}
+
+static void
+lockbutton_changed (UmLockButton *button,
+ gpointer data)
+{
+ UmLoginOptions *d = data;
+ gboolean authorized;
+
+ authorized = g_permission_get_allowed (G_PERMISSION (d->permission));
+
+ gtk_widget_set_sensitive (d->autologin_combo, authorized);
+ gtk_widget_set_sensitive (d->userlist_check, authorized);
+ gtk_widget_set_sensitive (d->power_check, authorized);
+ gtk_widget_set_sensitive (d->hints_check, authorized);
+ gtk_widget_set_sensitive (d->guest_check, authorized);
+}
+
+UmLoginOptions *
+um_login_options_new (GtkBuilder *builder)
+{
+ GtkWidget *widget;
+ GtkWidget *box;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GError *error;
+ UmLoginOptions *um;
+
+ /* TODO: get actual login screen options */
+
+ um = g_new0 (UmLoginOptions, 1);
+
+ um->manager = um_user_manager_ref_default ();
+ g_signal_connect (um->manager, "users-loaded",
+ G_CALLBACK (users_loaded), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-automatic-login-combobox");
+ um->autologin_combo = widget;
+
+ store = gtk_list_store_new (2, G_TYPE_STRING, UM_TYPE_USER);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (widget), GTK_TREE_MODEL (store));
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ AUTOLOGIN_NAME_COL, _("Disabled"),
+ AUTOLOGIN_USER_COL, NULL,
+ -1);
+
+ gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
+
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store), sort_login_users, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (update_autologin), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-show-user-list-checkbutton");
+ um->userlist_check = widget;
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (update_login_options), um);
+ g_object_set_data (G_OBJECT (widget), "gconf-key",
+ "/apps/gdm/simple-greeter/disable_user_list");
+ update_boolean_from_gconf (widget, um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-show-power-buttons-checkbutton");
+ um->power_check = widget;
+ g_signal_connect(widget, "toggled",
+ G_CALLBACK (update_login_options), um);
+ g_object_set_data (G_OBJECT (widget), "gconf-key",
+ "/apps/gdm/simple-greeter/disable_restart_buttons");
+ update_boolean_from_gconf (widget, um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-show-password-hints-checkbutton");
+ um->hints_check = widget;
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (update_login_options), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dm-allow-guest-login-checkbutton");
+ um->guest_check = widget;
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (update_login_options), um);
+
+ um->permission = polkit_permission_new_sync ("org.freedesktop.accounts.set-login-option", NULL, NULL, NULL);
+ widget = um_lock_button_new (um->permission);
+ gtk_widget_show (widget);
+ box = (GtkWidget *)gtk_builder_get_object (builder, "lockbutton-alignment");
+ gtk_container_add (GTK_CONTAINER (box), widget);
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (lockbutton_changed), um);
+ lockbutton_changed (UM_LOCK_BUTTON (widget), um);
+ um->lock_button = widget;
+
+ error = NULL;
+ um->connection = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (error != NULL) {
+ g_warning ("Failed to get system bus connection: %s", error->message);
+ g_error_free (error);
+ }
+
+ um->proxy = dbus_g_proxy_new_for_name (um->connection,
+ "org.gnome.GConf.Defaults",
+ "/",
+ "org.gnome.GConf.Defaults");
+
+ if (um->proxy == NULL) {
+ g_warning ("Cannot connect to GConf defaults mechanism");
+ }
+
+ return um;
+}
+
+void
+um_login_options_free (UmLoginOptions *um)
+{
+ if (um->manager)
+ g_object_unref (um->manager);
+ if (um->proxy)
+ g_object_unref (um->proxy);
+ if (um->connection)
+ dbus_g_connection_unref (um->connection);
+
+ g_free (um);
+}
+
diff --git a/panels/user-accounts/um-login-options.h b/panels/user-accounts/um-login-options.h
new file mode 100644
index 000000000..949e0c449
--- /dev/null
+++ b/panels/user-accounts/um-login-options.h
@@ -0,0 +1,34 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef __UM_LOGIN_OPTIONS_H__
+#define __UM_LOGIN_OPTIONS_H__
+
+G_BEGIN_DECLS
+
+typedef struct _UmLoginOptions UmLoginOptions;
+
+UmLoginOptions *um_login_options_new (GtkBuilder *builder);
+void um_login_options_free (UmLoginOptions *options);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-password-dialog.c b/panels/user-accounts/um-password-dialog.c
new file mode 100644
index 000000000..bd6ad8cf7
--- /dev/null
+++ b/panels/user-accounts/um-password-dialog.c
@@ -0,0 +1,834 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "um-password-dialog.h"
+#include "um-user-manager.h"
+#include "um-strength-bar.h"
+#include "um-utils.h"
+#include "run-passwd.h"
+
+#define MIN_PASSWORD_LEN 6
+
+struct _UmPasswordDialog {
+ GtkWidget *dialog;
+ GtkWidget *user_icon;
+ GtkWidget *user_name;
+ GtkWidget *action_label;
+ GtkWidget *action_combo;
+ GtkWidget *password_entry;
+ GtkWidget *verify_entry;
+ GtkWidget *strength_indicator;
+ GtkWidget *strength_indicator_label;
+ GtkWidget *normal_hint_entry;
+ GtkWidget *normal_hint_label;
+ GtkWidget *generate_button;
+ GtkWidget *generate_menu;
+ GtkWidget *show_password_button;
+ GtkWidget *ok_button;
+
+ UmUser *user;
+
+ GtkWidget *old_password_label;
+ GtkWidget *old_password_entry;
+ gboolean old_password_ok;
+
+ PasswdHandler *passwd_handler;
+};
+
+static void
+generate_clicked (GtkButton *button,
+ UmPasswordDialog *um)
+{
+ gtk_menu_popup (GTK_MENU (um->generate_menu),
+ NULL, NULL,
+ (GtkMenuPositionFunc) popup_menu_below_button, um->generate_button,
+ 0, gtk_get_current_event_time ());
+
+ gtk_widget_set_has_tooltip (um->generate_button, FALSE);
+}
+
+static void
+generate_draw (GtkWidget *widget,
+ cairo_t *cr,
+ UmPasswordDialog *um)
+{
+ GtkAllocation allocation;
+
+ if (!gtk_widget_is_sensitive (widget))
+ return;
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_paint_expander (gtk_widget_get_style (widget),
+ cr,
+ gtk_widget_get_state (widget),
+ widget,
+ NULL,
+ allocation.x + allocation.width,
+ allocation.y + allocation.height,
+ GTK_EXPANDER_EXPANDED);
+}
+
+static void
+activate_password_item (GtkMenuItem *item,
+ UmPasswordDialog *um)
+{
+ const char *password;
+
+ password = gtk_menu_item_get_label (item);
+
+ gtk_entry_set_text (GTK_ENTRY (um->password_entry), password);
+ gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->show_password_button), TRUE);
+ gtk_widget_grab_focus (um->verify_entry);
+}
+
+static void generate_passwords (UmPasswordDialog *um);
+
+static void
+activate_generate_item (GtkMenuItem *item,
+ UmPasswordDialog *um)
+{
+ generate_passwords (um);
+ generate_clicked (GTK_BUTTON (um->generate_button), um);
+}
+
+static void
+on_generate_menu_unmap (GtkWidget *menu,
+ UmPasswordDialog *um)
+{
+ gtk_widget_set_has_tooltip (um->generate_button, TRUE);
+}
+
+static void
+generate_passwords (UmPasswordDialog *um)
+{
+ gint min_len, max_len;
+ gchar *output, *err, *cmdline;
+ gint status;
+ GError *error;
+ gchar **lines;
+ gint i;
+ GtkWidget *item;
+
+ min_len = 6;
+ max_len = 12;
+
+ if (um->generate_menu) {
+ gtk_widget_destroy (um->generate_menu);
+ }
+
+ um->generate_menu = gtk_menu_new ();
+ g_signal_connect (um->generate_menu, "unmap",
+ G_CALLBACK (on_generate_menu_unmap), um);
+
+ cmdline = g_strdup_printf ("apg -n 6 -M SNC -m %d -x %d", min_len, max_len);
+ error = NULL;
+ output = NULL;
+ err = NULL;
+ if (!g_spawn_command_line_sync (cmdline, &output, &err, &status, &error)) {
+ g_warning ("Failed to run apg: %s", error->message);
+ g_error_free (error);
+ }
+
+ if (WEXITSTATUS (status) == 0) {
+ lines = g_strsplit (output, "\n", 0);
+ for (i = 0; lines[i]; i++) {
+ if (lines[i][0] == 0)
+ continue;
+
+ item = gtk_menu_item_new_with_label (lines[i]);
+ g_signal_connect (item, "activate",
+ G_CALLBACK (activate_password_item), um);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
+ }
+ g_strfreev (lines);
+ }
+ else {
+ g_warning ("agp returned an error: %s", err);
+ }
+
+ g_free (cmdline);
+ g_free (output);
+ g_free (err);
+
+ item = gtk_separator_menu_item_new ();
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
+
+ item = gtk_menu_item_new_with_label (_("More choices..."));
+ g_signal_connect (item, "activate",
+ G_CALLBACK (activate_generate_item), um);
+ gtk_widget_show (item);
+ gtk_menu_shell_append (GTK_MENU_SHELL (um->generate_menu), item);
+}
+
+/* This code is based on the Master Password dialog in Firefox
+ * (pref-masterpass.js)
+ * Original code triple-licensed under the MPL, GPL, and LGPL
+ * so is license-compatible with this file
+ */
+static gdouble
+compute_password_strength (const gchar *password)
+{
+ gint length;
+ gint upper, lower, digit, misc;
+ gint i;
+ gdouble strength;
+
+ length = strlen (password);
+ upper = 0;
+ lower = 0;
+ digit = 0;
+ misc = 0;
+
+ for (i = 0; i < length ; i++) {
+ if (g_ascii_isdigit (password[i]))
+ digit++;
+ else if (g_ascii_islower (password[i]))
+ lower++;
+ else if (g_ascii_isupper (password[i]))
+ upper++;
+ else
+ misc++;
+ }
+
+ if (length > 5)
+ length = 5;
+
+ if (digit > 3)
+ digit = 3;
+
+ if (upper > 3)
+ upper = 3;
+
+ if (misc > 3)
+ misc = 3;
+
+ strength = ((length * 0.1) - 0.2) +
+ (digit * 0.1) +
+ (misc * 0.15) +
+ (upper * 0.1);
+
+ strength = CLAMP (strength, 0.0, 1.0);
+
+ return strength;
+}
+
+static void
+finish_password_change (UmPasswordDialog *um)
+{
+ gtk_widget_hide (um->dialog);
+
+ gtk_entry_set_text (GTK_ENTRY (um->password_entry), " ");
+ gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->normal_hint_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
+
+ um_password_dialog_set_user (um, NULL);
+}
+
+static void
+cancel_password_dialog (GtkButton *button,
+ UmPasswordDialog *um)
+{
+ finish_password_change (um);
+}
+
+static void
+dialog_closed (GtkWidget *dialog,
+ gint response_id,
+ UmPasswordDialog *um)
+{
+ gtk_widget_destroy (dialog);
+}
+
+static void
+password_changed_cb (PasswdHandler *handler,
+ GError *error,
+ UmPasswordDialog *um)
+{
+ GtkWidget *dialog;
+ const gchar *primary_text;
+ const gchar *secondary_text;
+
+ gtk_widget_set_sensitive (um->dialog, TRUE);
+ gdk_window_set_cursor (gtk_widget_get_window (um->dialog), NULL);
+
+ if (!error) {
+ finish_password_change (um);
+ return;
+ }
+
+ if (error->code == PASSWD_ERROR_REJECTED) {
+ primary_text = error->message;
+ secondary_text = _("Please choose another password.");
+
+ gtk_entry_set_text (GTK_ENTRY (um->password_entry), "");
+ gtk_widget_grab_focus (um->password_entry);
+
+ gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+ }
+ else if (error->code == PASSWD_ERROR_AUTH_FAILED) {
+ primary_text = error->message;
+ secondary_text = _("Please type your current password again.");
+
+ gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
+ gtk_widget_grab_focus (um->old_password_entry);
+ }
+ else {
+ primary_text = _("Password could not be changed");
+ secondary_text = error->message;
+ }
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (um->dialog),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "%s", primary_text);
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", secondary_text);
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (dialog_closed), um);
+ gtk_window_present (GTK_WINDOW (dialog));
+
+}
+
+static void
+accept_password_dialog (GtkButton *button,
+ UmPasswordDialog *um)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint mode;
+ const gchar *hint;
+ const gchar *password;
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->action_combo));
+ gtk_combo_box_get_active_iter (GTK_COMBO_BOX (um->action_combo), &iter);
+ gtk_tree_model_get (model, &iter, 1, &mode, -1);
+
+ password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+ hint = gtk_entry_get_text (GTK_ENTRY (um->normal_hint_entry));
+
+ if (mode == 0 && um_user_get_uid (um->user) == getuid ()) {
+ GdkDisplay *display;
+ GdkCursor *cursor;
+
+ /* When setting a password for the current user,
+ * use passwd directly, to preserve the audit trail
+ * and to e.g. update the keyring password.
+ */
+ passwd_change_password (um->passwd_handler, password, (PasswdCallback) password_changed_cb, um);
+ gtk_widget_set_sensitive (um->dialog, FALSE);
+ display = gtk_widget_get_display (um->dialog);
+ cursor = gdk_cursor_new_for_display (display, GDK_WATCH);
+ gdk_window_set_cursor (gtk_widget_get_window (um->dialog), cursor);
+ gdk_display_flush (display);
+ gdk_cursor_unref (cursor);
+ }
+ else {
+ um_user_set_password (um->user, mode, password, hint);
+ finish_password_change (um);
+ }
+}
+
+static void
+update_sensitivity (UmPasswordDialog *um)
+{
+ const gchar *password, *verify;
+ const gchar *old_password;
+ const gchar *tooltip;
+ gboolean can_change;
+
+ password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+ verify = gtk_entry_get_text (GTK_ENTRY (um->verify_entry));
+ old_password = gtk_entry_get_text (GTK_ENTRY (um->old_password_entry));
+
+ /* TODO: configurable policies for acceptable passwords */
+ if (strlen (password) < MIN_PASSWORD_LEN) {
+ can_change = FALSE;
+ if (password[0] == '\0') {
+ tooltip = _("You need to enter a new password");
+ }
+ else {
+ tooltip = _("The new password is too short");
+ }
+ }
+ else if (strcmp (password, verify) != 0) {
+ can_change = FALSE;
+ if (verify[0] == '\0') {
+ tooltip = _("You need to confirm the password");
+ }
+ else {
+ tooltip = _("The passwords do not match");
+ }
+ }
+ else if (!um->old_password_ok) {
+ can_change = FALSE;
+ if (old_password[0] == '\0') {
+ tooltip = _("You need to enter your current password");
+ }
+ else {
+ tooltip = _("The current password is not correct");
+ }
+ }
+ else {
+ can_change = TRUE;
+ tooltip = NULL;
+ }
+
+ gtk_widget_set_sensitive (um->ok_button, can_change);
+ gtk_widget_set_tooltip_text (um->ok_button, tooltip);
+}
+
+static void
+action_changed (GtkComboBox *combo,
+ UmPasswordDialog *um)
+{
+ gint active;
+
+ active = gtk_combo_box_get_active (combo);
+ if (active == 0) {
+ gtk_widget_set_sensitive (um->password_entry, TRUE);
+ gtk_widget_set_sensitive (um->generate_button, TRUE);
+ gtk_widget_set_has_tooltip (um->generate_button, TRUE);
+ gtk_widget_set_sensitive (um->verify_entry, TRUE);
+ gtk_widget_set_sensitive (um->old_password_entry, TRUE);
+ gtk_widget_set_sensitive (um->normal_hint_entry, TRUE);
+ gtk_widget_set_sensitive (um->normal_hint_label, TRUE);
+ gtk_widget_set_sensitive (um->strength_indicator_label, TRUE);
+ gtk_widget_set_sensitive (um->show_password_button, TRUE);
+
+ update_sensitivity (um);
+ }
+ else {
+ gtk_widget_set_sensitive (um->password_entry, FALSE);
+ gtk_widget_set_sensitive (um->generate_button, FALSE);
+ gtk_widget_set_has_tooltip (um->generate_button, FALSE);
+ gtk_widget_set_sensitive (um->verify_entry, FALSE);
+ gtk_widget_set_sensitive (um->old_password_entry, FALSE);
+ gtk_widget_set_sensitive (um->normal_hint_entry, FALSE);
+ gtk_widget_set_sensitive (um->normal_hint_label, FALSE);
+ gtk_widget_set_sensitive (um->strength_indicator_label, FALSE);
+ gtk_widget_set_sensitive (um->show_password_button, FALSE);
+ gtk_widget_set_sensitive (um->ok_button, TRUE);
+ }
+}
+
+static void
+show_password_toggled (GtkToggleButton *button,
+ UmPasswordDialog *um)
+{
+ gboolean active;
+
+ active = gtk_toggle_button_get_active (button);
+ gtk_entry_set_visibility (GTK_ENTRY (um->password_entry), active);
+ gtk_entry_set_visibility (GTK_ENTRY (um->verify_entry), active);
+}
+
+static void
+update_password_strength (UmPasswordDialog *um)
+{
+ const gchar *password;
+ gdouble strength;
+ const gchar *hint;
+
+ password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+
+ strength = compute_password_strength (password);
+
+ if (strlen (password) < MIN_PASSWORD_LEN) {
+ strength = 0.0;
+ if (password[0] == '\0')
+ hint = "";
+ else
+ hint = C_("Password strength", "Too short");
+ }
+ else if (strength < 0.50)
+ hint = C_("Password strength", "Weak");
+ else if (strength < 0.75)
+ hint = C_("Password strength", "Fair");
+ else if (strength < 0.90)
+ hint = C_("Password strength", "Good");
+ else
+ hint = C_("Password strength", "Strong");
+
+ um_strength_bar_set_strength (UM_STRENGTH_BAR (um->strength_indicator), strength);
+ gtk_label_set_label (GTK_LABEL (um->strength_indicator_label), hint);
+}
+
+static void
+password_entry_changed (GtkEntry *entry,
+ GParamSpec *pspec,
+ UmPasswordDialog *um)
+{
+ update_password_strength (um);
+ update_sensitivity (um);
+}
+
+static void
+verify_entry_changed (GtkEntry *entry,
+ GParamSpec *pspec,
+ UmPasswordDialog *um)
+{
+ clear_entry_validation_error (GTK_ENTRY (entry));
+ update_password_strength (um);
+ update_sensitivity (um);
+}
+
+static gboolean
+verify_entry_focus_out (GtkWidget *entry,
+ GdkEventFocus *event,
+ UmPasswordDialog *um)
+{
+ const char *password;
+ const char *verify;
+
+ password = gtk_entry_get_text (GTK_ENTRY (um->password_entry));
+ verify = gtk_entry_get_text (GTK_ENTRY (um->verify_entry));
+
+ if (strlen (password) > 0 && strlen (verify) > 0) {
+ if (strcmp (password, verify) != 0) {
+ set_entry_validation_error (GTK_ENTRY (um->verify_entry),
+ _("Passwords do not match"));
+ }
+ else {
+ clear_entry_validation_error (GTK_ENTRY (um->verify_entry));
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+entry_size_changed (GtkWidget *entry,
+ GtkAllocation *allocation,
+ GtkWidget *label)
+{
+ gtk_widget_set_size_request (label, allocation->width, -1);
+}
+
+static void
+auth_cb (PasswdHandler *handler,
+ GError *error,
+ UmPasswordDialog *um)
+{
+ if (error) {
+ um->old_password_ok = FALSE;
+ set_entry_validation_error (GTK_ENTRY (um->old_password_entry),
+ _("Wrong password"));
+ }
+ else {
+ um->old_password_ok = TRUE;
+ clear_entry_validation_error (GTK_ENTRY (um->old_password_entry));
+ }
+
+ update_sensitivity (um);
+}
+
+static gboolean
+old_password_entry_focus_out (GtkWidget *entry,
+ GdkEventFocus *event,
+ UmPasswordDialog *um)
+{
+ const char *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ if (strlen (text) > 0) {
+ passwd_authenticate (um->passwd_handler, text,
+ (PasswdCallback)auth_cb, um);
+ }
+
+ return FALSE;
+}
+
+static void
+old_password_entry_activate (GtkWidget *entry,
+ UmPasswordDialog *um)
+{
+ const char *text;
+
+ text = gtk_entry_get_text (GTK_ENTRY (entry));
+ if (strlen (text) > 0) {
+ passwd_authenticate (um->passwd_handler, text,
+ (PasswdCallback)auth_cb, um);
+ }
+}
+
+
+static void
+old_password_entry_changed (GtkEntry *entry,
+ GParamSpec *pspec,
+ UmPasswordDialog *um)
+{
+ clear_entry_validation_error (GTK_ENTRY (entry));
+ um->old_password_ok = FALSE;
+ update_sensitivity (um);
+}
+
+void
+um_password_dialog_set_privileged (UmPasswordDialog *um,
+ gboolean privileged)
+{
+ if (privileged) {
+ gtk_widget_set_visible (um->action_label, TRUE);
+ gtk_widget_set_visible (um->action_combo, TRUE);
+ }
+ else {
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->action_combo), 0);
+ gtk_widget_set_visible (um->action_label, FALSE);
+ gtk_widget_set_visible (um->action_combo, FALSE);
+ }
+}
+
+UmPasswordDialog *
+um_password_dialog_new (void)
+{
+ GtkBuilder *builder;
+ GError *error;
+ const gchar *filename;
+ UmPasswordDialog *um;
+ GtkWidget *widget;
+ gint len;
+
+ builder = gtk_builder_new ();
+
+ error = NULL;
+ filename = UIDIR "/password-dialog.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+ filename = "../data/password-dialog.ui";
+ if (!gtk_builder_add_from_file (builder, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ um = g_new0 (UmPasswordDialog, 1);
+
+ um->action_label = (GtkWidget *) gtk_builder_get_object (builder, "action-label");
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "action-combo");
+ g_signal_connect (widget, "changed",
+ G_CALLBACK (action_changed), um);
+ um->action_combo = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+ g_signal_connect (widget, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+ um->dialog = widget;
+
+ um->user_icon = (GtkWidget *) gtk_builder_get_object (builder, "user-icon");
+ um->user_name = (GtkWidget *) gtk_builder_get_object (builder, "user-name");
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "cancel-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (cancel_password_dialog), um);
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (accept_password_dialog), um);
+ gtk_widget_grab_default (widget);
+ um->ok_button = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "show-password-checkbutton");
+ g_signal_connect (widget, "toggled",
+ G_CALLBACK (show_password_toggled), um);
+ um->show_password_button = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "password-entry");
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (password_entry_changed), um);
+ gtk_entry_set_visibility (GTK_ENTRY (widget), FALSE);
+
+ um->password_entry = widget;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "old-password-entry");
+ g_signal_connect_after (widget, "focus-out-event",
+ G_CALLBACK (old_password_entry_focus_out), um);
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (old_password_entry_changed), um);
+ g_signal_connect (widget, "activate",
+ G_CALLBACK (old_password_entry_activate), um);
+ um->old_password_entry = widget;
+ um->old_password_label = (GtkWidget *) gtk_builder_get_object (builder, "old-password-label");
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "verify-entry");
+ g_signal_connect (widget, "notify::text",
+ G_CALLBACK (verify_entry_changed), um);
+ g_signal_connect_after (widget, "focus-out-event",
+ G_CALLBACK (verify_entry_focus_out), um);
+ um->verify_entry = widget;
+
+ len = 0;
+ len = MAX (len, strlen (C_("Password strength", "Too short")));
+ len = MAX (len, strlen (C_("Password strength", "Weak")));
+ len = MAX (len, strlen (C_("Password strength", "Fair")));
+ len = MAX (len, strlen (C_("Password strength", "Good")));
+ len = MAX (len, strlen (C_("Password strength", "Strong")));
+ len += 2;
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator-label");
+ gtk_label_set_width_chars (GTK_LABEL (widget), len);
+
+
+ widget = (GtkWidget *) gtk_builder_get_object (builder, "generate-again-button");
+ g_signal_connect (widget, "clicked",
+ G_CALLBACK (generate_clicked), um);
+#if 0
+ g_signal_connect (widget, "state-changed",
+ G_CALLBACK (generate_state_changed), um);
+#endif
+ um->generate_button = widget;
+ g_signal_connect_after (gtk_bin_get_child (GTK_BIN (widget)), "draw",
+ G_CALLBACK (generate_draw), um);
+
+ um->normal_hint_entry = (GtkWidget *) gtk_builder_get_object (builder, "normal-hint-entry");
+
+ /* Label size hack.
+ * This only sort-of works because the dialog is non-resizable.
+ */
+ widget = (GtkWidget *)gtk_builder_get_object (builder, "password-normal-hint-description-label");
+ g_signal_connect (um->normal_hint_entry, "size-allocate",
+ G_CALLBACK (entry_size_changed), widget);
+ um->normal_hint_label = widget;
+
+ um->strength_indicator = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator");
+
+ um->strength_indicator_label = (GtkWidget *) gtk_builder_get_object (builder, "strength-indicator-label");
+
+ g_object_unref (builder);
+
+ generate_passwords (um);
+
+ return um;
+}
+
+void
+um_password_dialog_free (UmPasswordDialog *um)
+{
+ gtk_widget_destroy (um->dialog);
+
+ if (um->user)
+ g_object_unref (um->user);
+
+ if (um->passwd_handler)
+ passwd_destroy (um->passwd_handler);
+
+ g_free (um);
+}
+
+static gboolean
+visible_func (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ UmPasswordDialog *um)
+{
+ if (um->user) {
+ gint mode;
+ gboolean locked = um_user_get_locked (um->user);
+
+ gtk_tree_model_get (model, iter, 1, &mode, -1);
+
+ if (mode == 3 && locked)
+ return FALSE;
+
+ if (mode == 4 && !locked)
+ return FALSE;
+
+ return TRUE;
+ }
+
+ return TRUE;
+}
+
+void
+um_password_dialog_set_user (UmPasswordDialog *um,
+ UmUser *user)
+{
+ GdkPixbuf *pixbuf;
+ GtkTreeModel *model;
+
+ if (um->user) {
+ g_object_unref (um->user);
+ um->user = NULL;
+ }
+ if (user) {
+ um->user = g_object_ref (user);
+
+ pixbuf = um_user_render_icon (user, FALSE, 48);
+ gtk_image_set_from_pixbuf (GTK_IMAGE (um->user_icon), pixbuf);
+ g_object_unref (pixbuf);
+
+ gtk_label_set_label (GTK_LABEL (um->user_name),
+ um_user_get_real_name (user));
+
+ gtk_entry_set_text (GTK_ENTRY (um->password_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->verify_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->normal_hint_entry), "");
+ gtk_entry_set_text (GTK_ENTRY (um->old_password_entry), "");
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->show_password_button), FALSE);
+ if (um_user_get_uid (um->user) == getuid()) {
+ gtk_widget_show (um->old_password_label);
+ gtk_widget_show (um->old_password_entry);
+ if (um->passwd_handler != NULL)
+ passwd_destroy (um->passwd_handler);
+ um->passwd_handler = passwd_init ();
+ um->old_password_ok = FALSE;
+ }
+ else {
+ gtk_widget_hide (um->old_password_label);
+ gtk_widget_hide (um->old_password_entry);
+ um->old_password_ok = TRUE;
+ }
+ }
+
+ model = gtk_combo_box_get_model (GTK_COMBO_BOX (um->action_combo));
+ if (!GTK_IS_TREE_MODEL_FILTER (model)) {
+ model = gtk_tree_model_filter_new (model, NULL);
+ gtk_combo_box_set_model (GTK_COMBO_BOX (um->action_combo), model);
+ gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (model),
+ (GtkTreeModelFilterVisibleFunc) visible_func,
+ um, NULL);
+ }
+
+ gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (model));
+ gtk_combo_box_set_active (GTK_COMBO_BOX (um->action_combo), 0);
+}
+
+void
+um_password_dialog_show (UmPasswordDialog *um,
+ GtkWindow *parent)
+{
+ gtk_window_set_transient_for (GTK_WINDOW (um->dialog), parent);
+ gtk_window_present (GTK_WINDOW (um->dialog));
+ gtk_widget_grab_focus (um->password_entry);
+}
+
diff --git a/panels/user-accounts/um-password-dialog.h b/panels/user-accounts/um-password-dialog.h
new file mode 100644
index 000000000..8354e7c9d
--- /dev/null
+++ b/panels/user-accounts/um-password-dialog.h
@@ -0,0 +1,43 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef __UM_PASSWORD_DIALOG_H__
+#define __UM_PASSWORD_DIALOG_H__
+
+#include
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+typedef struct _UmPasswordDialog UmPasswordDialog;
+
+UmPasswordDialog *um_password_dialog_new (void);
+void um_password_dialog_free (UmPasswordDialog *dialog);
+void um_password_dialog_set_user (UmPasswordDialog *dialog,
+ UmUser *user);
+void um_password_dialog_set_privileged (UmPasswordDialog *dialog,
+ gboolean privileged);
+void um_password_dialog_show (UmPasswordDialog *dialog,
+ GtkWindow *parent);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-photo-dialog.c b/panels/user-accounts/um-photo-dialog.c
new file mode 100644
index 000000000..2061ebeb8
--- /dev/null
+++ b/panels/user-accounts/um-photo-dialog.c
@@ -0,0 +1,693 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+
+#include
+#include
+#include
+#define GNOME_DESKTOP_USE_UNSTABLE_API
+#include
+
+#ifdef HAVE_CHEESE
+#include
+#include
+#endif /* HAVE_CHEESE */
+
+#include "um-photo-dialog.h"
+#include "um-user-manager.h"
+#include "um-crop-area.h"
+#include "um-utils.h"
+
+#define ROW_SPAN 6
+
+struct _UmPhotoDialog {
+ GtkWidget *photo_popup;
+ GtkWidget *popup_button;
+ GtkWidget *crop_area;
+
+#ifdef HAVE_CHEESE
+ CheeseCameraDeviceMonitor *monitor;
+ GtkWidget *take_photo_menuitem;
+ guint num_cameras;
+#endif /* HAVE_CHEESE */
+
+ GnomeDesktopThumbnailFactory *thumb_factory;
+
+ UmUser *user;
+};
+
+static void
+crop_dialog_response (GtkWidget *dialog,
+ gint response_id,
+ UmPhotoDialog *um)
+{
+ GdkPixbuf *pb, *pb2;
+
+ if (response_id != GTK_RESPONSE_ACCEPT) {
+ um->crop_area = NULL;
+ gtk_widget_destroy (dialog);
+ return;
+ }
+
+ pb = um_crop_area_get_picture (UM_CROP_AREA (um->crop_area));
+ pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR);
+
+ um_user_set_icon_data (um->user, pb2);
+
+ g_object_unref (pb2);
+ g_object_unref (pb);
+
+ um->crop_area = NULL;
+ gtk_widget_destroy (dialog);
+}
+
+static void
+um_photo_dialog_crop (UmPhotoDialog *um,
+ GdkPixbuf *pixbuf)
+{
+ GtkWidget *dialog;
+ GtkWidget *frame;
+
+ dialog = gtk_dialog_new_with_buttons ("",
+ GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)),
+ 0,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_REJECT,
+ _("Select"),
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users");
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (crop_dialog_response), um);
+
+ /* Content */
+ um->crop_area = um_crop_area_new ();
+ um_crop_area_set_min_size (UM_CROP_AREA (um->crop_area), 48, 48);
+ um_crop_area_set_constrain_aspect (UM_CROP_AREA (um->crop_area), TRUE);
+ um_crop_area_set_picture (UM_CROP_AREA (um->crop_area), pixbuf);
+ frame = gtk_frame_new (NULL);
+ gtk_container_add (GTK_CONTAINER (frame), um->crop_area);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
+
+ gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+ frame,
+ TRUE, TRUE, 8);
+
+ gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 300);
+
+ gtk_widget_show_all (dialog);
+}
+
+static void
+file_chooser_response (GtkDialog *chooser,
+ gint response,
+ UmPhotoDialog *um)
+{
+ gchar *filename;
+ GError *error;
+ GdkPixbuf *pixbuf;
+
+ if (response != GTK_RESPONSE_ACCEPT) {
+ gtk_widget_destroy (GTK_WIDGET (chooser));
+ return;
+ }
+
+ filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (chooser));
+
+ error = NULL;
+ pixbuf = gdk_pixbuf_new_from_file (filename, &error);
+ if (pixbuf == NULL) {
+ g_warning ("Failed to load %s: %s", filename, error->message);
+ g_error_free (error);
+ }
+ g_free (filename);
+
+ gtk_widget_destroy (GTK_WIDGET (chooser));
+
+ um_photo_dialog_crop (um, pixbuf);
+ g_object_unref (pixbuf);
+}
+
+static void
+update_preview (GtkFileChooser *chooser,
+ GnomeDesktopThumbnailFactory *thumb_factory)
+{
+ gchar *uri;
+
+ uri = gtk_file_chooser_get_preview_uri (chooser);
+
+ if (uri) {
+ GdkPixbuf *pixbuf = NULL;
+ const gchar *mime_type = NULL;
+ GFile *file;
+ GFileInfo *file_info;
+ GtkWidget *preview;
+
+ preview = gtk_file_chooser_get_preview_widget (chooser);
+
+ file = g_file_new_for_uri (uri);
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
+ G_FILE_QUERY_INFO_NONE,
+ NULL, NULL);
+ g_object_unref (file);
+
+ if (file_info != NULL) {
+ mime_type = g_file_info_get_content_type (file_info);
+ g_object_unref (file_info);
+ }
+
+ if (mime_type) {
+ pixbuf = gnome_desktop_thumbnail_factory_generate_thumbnail (thumb_factory,
+ uri,
+ mime_type);
+ }
+
+ if (pixbuf != NULL) {
+ gtk_image_set_from_pixbuf (GTK_IMAGE (preview), pixbuf);
+ g_object_unref (pixbuf);
+ }
+ else {
+ gtk_image_set_from_stock (GTK_IMAGE (preview),
+ GTK_STOCK_DIALOG_QUESTION,
+ GTK_ICON_SIZE_DIALOG);
+ }
+
+ g_free (uri);
+ }
+
+ gtk_file_chooser_set_preview_widget_active (chooser, TRUE);
+}
+
+static void
+um_photo_dialog_select_file (UmPhotoDialog *um)
+{
+ GtkWidget *chooser;
+ const gchar *folder;
+ GtkWidget *preview;
+
+ chooser = gtk_file_chooser_dialog_new (_("Browse for more pictures"),
+ GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
+ NULL);
+
+ gtk_window_set_modal (GTK_WINDOW (chooser), TRUE);
+
+ preview = gtk_image_new ();
+ gtk_widget_set_size_request (preview, 128, -1);
+ gtk_file_chooser_set_preview_widget (GTK_FILE_CHOOSER (chooser), preview);
+ gtk_file_chooser_set_use_preview_label (GTK_FILE_CHOOSER (chooser), FALSE);
+ gtk_widget_show (preview);
+ g_signal_connect (chooser, "update-preview",
+ G_CALLBACK (update_preview), um->thumb_factory);
+
+ folder = g_get_user_special_dir (G_USER_DIRECTORY_PICTURES);
+ if (folder)
+ gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (chooser),
+ folder);
+
+ g_signal_connect (chooser, "response",
+ G_CALLBACK (file_chooser_response), um);
+
+ gtk_window_present (GTK_WINDOW (chooser));
+}
+
+static void
+none_icon_selected (GtkMenuItem *menuitem,
+ UmPhotoDialog *um)
+{
+ um_user_set_icon_file (um->user, NULL);
+}
+
+static void
+file_icon_selected (GtkMenuItem *menuitem,
+ UmPhotoDialog *um)
+{
+ um_photo_dialog_select_file (um);
+}
+
+#ifdef HAVE_CHEESE
+static gboolean
+destroy_chooser (GtkWidget *chooser)
+{
+ gtk_widget_destroy (chooser);
+ return FALSE;
+}
+
+static void
+webcam_response_cb (GtkDialog *dialog,
+ int response,
+ UmPhotoDialog *um)
+{
+ if (response == GTK_RESPONSE_ACCEPT) {
+ GdkPixbuf *pb, *pb2;
+
+ g_object_get (G_OBJECT (dialog), "pixbuf", &pb, NULL);
+ pb2 = gdk_pixbuf_scale_simple (pb, 96, 96, GDK_INTERP_BILINEAR);
+
+ um_user_set_icon_data (um->user, pb2);
+
+ g_object_unref (pb2);
+ g_object_unref (pb);
+ }
+ if (response != GTK_RESPONSE_DELETE_EVENT &&
+ response != GTK_RESPONSE_NONE)
+ g_idle_add ((GSourceFunc) destroy_chooser, dialog);
+}
+
+static void
+webcam_icon_selected (GtkMenuItem *menuitem,
+ UmPhotoDialog *um)
+{
+ GtkWidget *window;
+
+ window = cheese_avatar_chooser_new ();
+ gtk_window_set_transient_for (GTK_WINDOW (window),
+ GTK_WINDOW (gtk_widget_get_toplevel (um->popup_button)));
+ gtk_window_set_modal (GTK_WINDOW (window), TRUE);
+ g_signal_connect (G_OBJECT (window), "response",
+ G_CALLBACK (webcam_response_cb), um);
+ gtk_widget_show (window);
+}
+
+static void
+update_photo_menu_status (UmPhotoDialog *um)
+{
+ if (um->num_cameras == 0)
+ gtk_widget_set_sensitive (um->take_photo_menuitem, FALSE);
+ else
+ gtk_widget_set_sensitive (um->take_photo_menuitem, TRUE);
+}
+
+static void
+device_added (CheeseCameraDeviceMonitor *monitor,
+ const gchar *id,
+ const gchar *device_file,
+ const gchar *product_name,
+ gint api_version,
+ UmPhotoDialog *um)
+{
+ um->num_cameras++;
+ update_photo_menu_status (um);
+}
+
+static void
+device_removed (CheeseCameraDeviceMonitor *monitor,
+ const char *id,
+ UmPhotoDialog *um)
+{
+ um->num_cameras--;
+ update_photo_menu_status (um);
+}
+
+#endif /* HAVE_CHEESE */
+
+static void
+stock_icon_selected (GtkMenuItem *menuitem,
+ UmPhotoDialog *um)
+{
+ const char *filename;
+
+ filename = g_object_get_data (G_OBJECT (menuitem), "filename");
+ um_user_set_icon_file (um->user, filename);
+}
+
+static GtkWidget *
+menu_item_for_filename (UmPhotoDialog *um,
+ const char *filename)
+{
+ GtkWidget *image, *menuitem;
+ GFile *file;
+ GIcon *icon;
+
+ file = g_file_new_for_path (filename);
+ icon = g_file_icon_new (file);
+ g_object_unref (file);
+ image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_DIALOG);
+ g_object_unref (icon);
+
+ menuitem = gtk_menu_item_new ();
+ gtk_container_add (GTK_CONTAINER (menuitem), image);
+ gtk_widget_show_all (menuitem);
+
+ g_object_set_data_full (G_OBJECT (menuitem), "filename",
+ g_strdup (filename), (GDestroyNotify) g_free);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (stock_icon_selected), um);
+
+ return menuitem;
+}
+
+static void
+setup_photo_popup (UmPhotoDialog *um)
+{
+ GtkWidget *menu, *menuitem, *image;
+ guint x, y;
+ GDir *dir;
+ const char *face;
+ GError *error;
+
+ menu = gtk_menu_new ();
+
+ x = 0;
+ y = 0;
+
+ error = NULL;
+ dir = g_dir_open (DATADIR "/pixmaps/faces", 0, &error);
+ if (dir == NULL) {
+ g_warning ("Failed to load faces: %s", error->message);
+ g_error_free (error);
+ goto skip_faces;
+ }
+
+ while ((face = g_dir_read_name (dir)) != NULL) {
+ char *filename;
+
+ filename = g_build_filename (DATADIR "/pixmaps/faces", face, NULL);
+ menuitem = menu_item_for_filename (um, filename);
+ g_free (filename);
+ if (menuitem == NULL)
+ continue;
+
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+ x, x + 1, y, y + 1);
+ gtk_widget_show (menuitem);
+
+ x++;
+ if (x >= ROW_SPAN - 1) {
+ y++;
+ x = 0;
+ }
+ }
+ g_dir_close (dir);
+
+ image = gtk_image_new_from_icon_name ("avatar-default", GTK_ICON_SIZE_DIALOG);
+ menuitem = gtk_menu_item_new ();
+ gtk_container_add (GTK_CONTAINER (menuitem), image);
+ gtk_widget_show_all (menuitem);
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+ x, x + 1, y, y + 1);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (none_icon_selected), um);
+ gtk_widget_show (menuitem);
+ y++;
+
+ /* Separator */
+ menuitem = gtk_separator_menu_item_new ();
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+ 0, ROW_SPAN - 1, y, y + 1);
+ gtk_widget_show (menuitem);
+
+ y++;
+
+skip_faces:
+
+#ifdef HAVE_CHEESE
+ um->take_photo_menuitem = gtk_menu_item_new_with_label (_("Take a photo..."));
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (um->take_photo_menuitem),
+ 0, ROW_SPAN - 1, y, y + 1);
+ g_signal_connect (G_OBJECT (um->take_photo_menuitem), "activate",
+ G_CALLBACK (webcam_icon_selected), um);
+ gtk_widget_set_sensitive (um->take_photo_menuitem, FALSE);
+ gtk_widget_show (um->take_photo_menuitem);
+
+ um->monitor = cheese_camera_device_monitor_new ();
+ g_signal_connect (G_OBJECT (um->monitor), "added",
+ G_CALLBACK (device_added), um);
+ g_signal_connect (G_OBJECT (um->monitor), "removed",
+ G_CALLBACK (device_removed), um);
+ cheese_camera_device_monitor_coldplug (um->monitor);
+
+ y++;
+#endif /* HAVE_CHEESE */
+
+ menuitem = gtk_menu_item_new_with_label (_("Browse for more pictures..."));
+ gtk_menu_attach (GTK_MENU (menu), GTK_WIDGET (menuitem),
+ 0, ROW_SPAN - 1, y, y + 1);
+ g_signal_connect (G_OBJECT (menuitem), "activate",
+ G_CALLBACK (file_icon_selected), um);
+ gtk_widget_show (menuitem);
+
+ um->photo_popup = menu;
+}
+
+static void
+popup_icon_menu (GtkToggleButton *button, UmPhotoDialog *um)
+{
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)) && !gtk_widget_get_visible (um->photo_popup)) {
+ gtk_menu_popup (GTK_MENU (um->photo_popup),
+ NULL, NULL,
+ (GtkMenuPositionFunc) popup_menu_below_button, um->popup_button,
+ 0, gtk_get_current_event_time ());
+ } else {
+ gtk_menu_popdown (GTK_MENU (um->photo_popup));
+ }
+}
+
+static gboolean
+on_popup_button_button_pressed (GtkToggleButton *button,
+ GdkEventButton *event,
+ UmPhotoDialog *um)
+{
+ if (event->button == 1) {
+ if (!gtk_widget_get_visible (um->photo_popup)) {
+ popup_icon_menu (button, um);
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), TRUE);
+ } else {
+ gtk_menu_popdown (GTK_MENU (um->photo_popup));
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (button), FALSE);
+ }
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+on_photo_popup_unmap (GtkWidget *popup_menu,
+ UmPhotoDialog *um)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (um->popup_button), FALSE);
+}
+
+static void
+popup_button_draw (GtkWidget *widget,
+ cairo_t *cr,
+ UmPhotoDialog *um)
+{
+ GtkAllocation allocation;
+
+ if (gtk_widget_get_state (widget) != GTK_STATE_PRELIGHT &&
+ !gtk_widget_is_focus (gtk_widget_get_parent (widget))) {
+ return;
+ }
+
+ gtk_widget_get_allocation (widget, &allocation);
+ gtk_paint_expander (gtk_widget_get_style (widget),
+ cr,
+ gtk_widget_get_state (widget),
+ widget,
+ NULL,
+ allocation.x + allocation.width,
+ allocation.y + allocation.height,
+ GTK_EXPANDER_EXPANDED);
+}
+
+static void
+popup_button_focus_changed (GObject *button,
+ GParamSpec *pspec,
+ UmPhotoDialog *um)
+{
+ gtk_widget_queue_draw (gtk_bin_get_child (GTK_BIN (button)));
+}
+
+UmPhotoDialog *
+um_photo_dialog_new (GtkWidget *button)
+{
+ UmPhotoDialog *um;
+
+ um = g_new0 (UmPhotoDialog, 1);
+
+ um->thumb_factory = gnome_desktop_thumbnail_factory_new (GNOME_DESKTOP_THUMBNAIL_SIZE_NORMAL);
+
+ /* Set up the popup */
+ um->popup_button = button;
+ setup_photo_popup (um);
+ g_signal_connect (button, "toggled",
+ G_CALLBACK (popup_icon_menu), um);
+ g_signal_connect (button, "button-press-event",
+ G_CALLBACK (on_popup_button_button_pressed), um);
+ g_signal_connect (button, "notify::is-focus",
+ G_CALLBACK (popup_button_focus_changed), um);
+ g_signal_connect_after (gtk_bin_get_child (GTK_BIN (button)), "draw",
+ G_CALLBACK (popup_button_draw), um);
+
+ g_signal_connect (um->photo_popup, "unmap",
+ G_CALLBACK (on_photo_popup_unmap), um);
+
+ return um;
+}
+
+void
+um_photo_dialog_free (UmPhotoDialog *um)
+{
+ gtk_widget_destroy (um->photo_popup);
+
+ if (um->thumb_factory)
+ g_object_unref (um->thumb_factory);
+#ifdef HAVE_CHEESE
+ if (um->monitor)
+ g_object_unref (um->monitor);
+#endif
+ if (um->user)
+ g_object_unref (um->user);
+
+ g_free (um);
+}
+
+static void
+clear_tip (GtkMenuItem *item,
+ gpointer user_data)
+{
+ GList *children;
+ GtkWidget *image;
+ GIcon *icon, *icon2;
+ const char *filename;
+
+ /* Not a stock icon? */
+ filename = g_object_get_data (G_OBJECT (item), "filename");
+ if (filename == NULL)
+ return;
+
+ children = gtk_container_get_children (GTK_CONTAINER (item));
+ image = children->data;
+ g_assert (image != NULL);
+ g_list_free (children);
+
+ gtk_image_get_gicon (GTK_IMAGE (image), &icon, NULL);
+
+ if (G_IS_EMBLEMED_ICON (icon))
+ icon2 = g_emblemed_icon_get_icon (G_EMBLEMED_ICON (icon));
+ else
+ return;
+
+ gtk_image_set_from_gicon (GTK_IMAGE (image), icon2, GTK_ICON_SIZE_DIALOG);
+ g_object_unref (icon);
+}
+
+static void
+set_tip (GtkWidget *item,
+ const char *tip,
+ GEmblem *emblem)
+{
+ GList *children;
+ GtkWidget *image;
+ GIcon *icon, *icon2;
+
+ children = gtk_container_get_children (GTK_CONTAINER (item));
+ image = children->data;
+ g_assert (image != NULL);
+ g_list_free (children);
+
+ gtk_image_get_gicon (GTK_IMAGE (image), &icon, NULL);
+ if (G_IS_EMBLEMED_ICON (icon)) {
+ return;
+ }
+
+ icon2 = g_emblemed_icon_new (icon, emblem);
+ gtk_image_set_from_gicon (GTK_IMAGE (image), icon2, GTK_ICON_SIZE_DIALOG);
+
+ gtk_widget_set_tooltip_text (GTK_WIDGET (item), tip);
+}
+
+void
+um_photo_dialog_set_user (UmPhotoDialog *um,
+ UmUser *user)
+{
+ UmUserManager *manager;
+ GSList *list, *l;
+ UmUser *u;
+ GIcon *icon;
+ GEmblem *emblem;
+ GList *children, *c;
+
+ g_return_if_fail (um != NULL);
+
+ if (um->user) {
+ g_object_unref (um->user);
+ um->user = NULL;
+ }
+ um->user = user;
+
+ if (um->user) {
+ g_object_ref (um->user);
+
+ children = gtk_container_get_children (GTK_CONTAINER (um->photo_popup));
+ g_list_foreach (children, (GFunc) clear_tip, NULL);
+
+ manager = um_user_manager_ref_default ();
+ list = um_user_manager_list_users (manager);
+ g_object_unref (manager);
+
+ icon = g_themed_icon_new ("avatar-default");
+ emblem = g_emblem_new (icon);
+ g_object_unref (icon);
+
+ for (l = list; l; l = l->next) {
+ const char *filename;
+
+ u = l->data;
+ if (u == user)
+ continue;
+ filename = um_user_get_icon_file (u);
+ if (filename == NULL)
+ continue;
+ for (c = children; c; c = c->next) {
+ const char *f;
+
+ f = g_object_get_data (G_OBJECT (c->data), "filename");
+ if (f == NULL)
+ continue;
+ if (strcmp (f, filename) == 0) {
+ char *tip;
+
+ tip = g_strdup_printf (_("Used by %s"),
+ um_user_get_real_name (u));
+ set_tip (GTK_WIDGET (c->data), tip, emblem);
+ g_free (tip);
+ break;
+ }
+ }
+ }
+ g_slist_free (list);
+
+ g_object_unref (emblem);
+ }
+}
+
diff --git a/panels/user-accounts/um-photo-dialog.h b/panels/user-accounts/um-photo-dialog.h
new file mode 100644
index 000000000..3f0c4047e
--- /dev/null
+++ b/panels/user-accounts/um-photo-dialog.h
@@ -0,0 +1,39 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef __UM_PHOTO_DIALOG_H__
+#define __UM_PHOTO_DIALOG_H__
+
+#include
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+typedef struct _UmPhotoDialog UmPhotoDialog;
+
+UmPhotoDialog *um_photo_dialog_new (GtkWidget *button);
+void um_photo_dialog_free (UmPhotoDialog *dialog);
+void um_photo_dialog_set_user (UmPhotoDialog *dialog,
+ UmUser *user);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-strength-bar.c b/panels/user-accounts/um-strength-bar.c
new file mode 100644
index 000000000..64af45f1c
--- /dev/null
+++ b/panels/user-accounts/um-strength-bar.c
@@ -0,0 +1,261 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+
+#include "um-strength-bar.h"
+
+#define GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), UM_TYPE_STRENGTH_BAR, UmStrengthBarPrivate))
+
+struct _UmStrengthBarPrivate {
+ gdouble strength;
+};
+
+enum {
+ PROP_0,
+ PROP_STRENGTH
+};
+
+G_DEFINE_TYPE (UmStrengthBar, um_strength_bar, GTK_TYPE_WIDGET);
+
+
+static void
+um_strength_bar_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ UmStrengthBar *bar = UM_STRENGTH_BAR (object);
+
+ switch (prop_id) {
+ case PROP_STRENGTH:
+ um_strength_bar_set_strength (bar, g_value_get_double (value));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+um_strength_bar_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ UmStrengthBar *bar = UM_STRENGTH_BAR (object);
+
+ switch (prop_id) {
+ case PROP_STRENGTH:
+ g_value_set_double (value, um_strength_bar_get_strength (bar));
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+curved_rectangle (cairo_t *cr,
+ double x0,
+ double y0,
+ double width,
+ double height,
+ double radius)
+{
+ double x1;
+ double y1;
+
+ x1 = x0 + width;
+ y1 = y0 + height;
+
+ if (!width || !height) {
+ return;
+ }
+
+ if (width / 2 < radius) {
+ if (height / 2 < radius) {
+ cairo_move_to (cr, x0, (y0 + y1) / 2);
+ cairo_curve_to (cr, x0 ,y0, x0, y0, (x0 + x1) / 2, y0);
+ cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
+ cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
+ cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
+ } else {
+ cairo_move_to (cr, x0, y0 + radius);
+ cairo_curve_to (cr, x0, y0, x0, y0, (x0 + x1) / 2, y0);
+ cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
+ cairo_line_to (cr, x1, y1 - radius);
+ cairo_curve_to (cr, x1, y1, x1, y1, (x1 + x0) / 2, y1);
+ cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius);
+ }
+ } else {
+ if (height / 2 < radius) {
+ cairo_move_to (cr, x0, (y0 + y1) / 2);
+ cairo_curve_to (cr, x0, y0, x0 , y0, x0 + radius, y0);
+ cairo_line_to (cr, x1 - radius, y0);
+ cairo_curve_to (cr, x1, y0, x1, y0, x1, (y0 + y1) / 2);
+ cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
+ cairo_line_to (cr, x0 + radius, y1);
+ cairo_curve_to (cr, x0, y1, x0, y1, x0, (y0 + y1) / 2);
+ } else {
+ cairo_move_to (cr, x0, y0 + radius);
+ cairo_curve_to (cr, x0 , y0, x0 , y0, x0 + radius, y0);
+ cairo_line_to (cr, x1 - radius, y0);
+ cairo_curve_to (cr, x1, y0, x1, y0, x1, y0 + radius);
+ cairo_line_to (cr, x1, y1 - radius);
+ cairo_curve_to (cr, x1, y1, x1, y1, x1 - radius, y1);
+ cairo_line_to (cr, x0 + radius, y1);
+ cairo_curve_to (cr, x0, y1, x0, y1, x0, y1 - radius);
+ }
+ }
+
+ cairo_close_path (cr);
+}
+
+gdouble color1[3] = { 213.0/255.0, 4.0/255.0, 4.0/255.0 };
+gdouble color2[3] = { 234.0/255.0, 236.0/255.0, 31.0/255.0 };
+gdouble color3[3] = { 141.0/255.0, 133.0/255.0, 241.0/255.0 };
+gdouble color4[3] = { 99.0/255.0, 251.0/255.0, 107.0/255.0 };
+
+static void
+get_color (gdouble value, gdouble *r, gdouble *g, gdouble *b)
+{
+ gdouble *c;
+
+ if (value < 0.50) {
+ c = color1;
+ }
+ else if (value < 0.75) {
+ c = color2;
+ }
+ else if (value < 0.90) {
+ c = color3;
+ }
+ else {
+ c = color4;
+ }
+
+ *r = c[0];
+ *g = c[1];
+ *b = c[2];
+}
+
+static gboolean
+um_strength_bar_draw (GtkWidget *widget,
+ cairo_t *cr)
+{
+ UmStrengthBar *bar = UM_STRENGTH_BAR (widget);
+ gdouble r, g, b;
+ GdkWindow *window;
+ GtkAllocation allocation;
+
+ window = gtk_widget_get_window (widget);
+ gtk_widget_get_allocation (widget, &allocation);
+ cr = gdk_cairo_create (window);
+ cairo_set_line_width (cr, 1);
+
+ cairo_rectangle (cr,
+ allocation.x,
+ allocation.y,
+ bar->priv->strength * allocation.width,
+ allocation.height);
+ cairo_clip (cr);
+
+ curved_rectangle (cr,
+ allocation.x + 0.5,
+ allocation.y + 0.5,
+ allocation.width - 1,
+ allocation.height - 1,
+ 4);
+ get_color (bar->priv->strength, &r, &g ,&b);
+ cairo_set_source_rgb (cr, r, g, b);
+ cairo_fill_preserve (cr);
+
+ cairo_reset_clip (cr);
+ cairo_set_source_rgb (cr, 0, 0, 0);
+ cairo_stroke (cr);
+
+ return FALSE;
+}
+
+static void
+um_strength_bar_class_init (UmStrengthBarClass *class)
+{
+ GObjectClass *gobject_class;
+ GtkWidgetClass *widget_class;
+
+ gobject_class = (GObjectClass*)class;
+ widget_class = (GtkWidgetClass*)class;
+
+ gobject_class->set_property = um_strength_bar_set_property;
+ gobject_class->get_property = um_strength_bar_get_property;
+
+ widget_class->draw = um_strength_bar_draw;
+
+ g_object_class_install_property (gobject_class,
+ PROP_STRENGTH,
+ g_param_spec_double ("strength",
+ "Strength",
+ "Strength",
+ 0.0, 1.0, 0.0,
+ G_PARAM_READWRITE));
+
+ g_type_class_add_private (class, sizeof (UmStrengthBarPrivate));
+}
+
+static void
+um_strength_bar_init (UmStrengthBar *bar)
+{
+ gtk_widget_set_has_window (GTK_WIDGET (bar), FALSE);
+ gtk_widget_set_size_request (GTK_WIDGET (bar), 120, 8);
+
+ bar->priv = GET_PRIVATE (bar);
+ bar->priv->strength = 0.0;
+}
+
+GtkWidget *
+um_strength_bar_new (void)
+{
+ return (GtkWidget*) g_object_new (UM_TYPE_STRENGTH_BAR, NULL);
+}
+
+void
+um_strength_bar_set_strength (UmStrengthBar *bar,
+ gdouble strength)
+{
+ bar->priv->strength = strength;
+
+ g_object_notify (G_OBJECT (bar), "strength");
+
+ if (gtk_widget_is_drawable (GTK_WIDGET (bar))) {
+ gtk_widget_queue_draw (GTK_WIDGET (bar));
+ }
+}
+
+gdouble
+um_strength_bar_get_strength (UmStrengthBar *bar)
+{
+ return bar->priv->strength;
+}
diff --git a/panels/user-accounts/um-strength-bar.h b/panels/user-accounts/um-strength-bar.h
new file mode 100644
index 000000000..8bf057297
--- /dev/null
+++ b/panels/user-accounts/um-strength-bar.h
@@ -0,0 +1,62 @@
+/*
+ * Copyright © 2010 Red Hat, Inc.
+ *
+ * Licensed under the GNU General Public License Version 3
+ *
+ * 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 3 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 .
+ *
+ * Author: Matthias Clasen
+ */
+
+#ifndef _UM_STRENGTH_BAR_H_
+#define _UM_STRENGTH_BAR_H_
+
+#include
+#include
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_STRENGTH_BAR (um_strength_bar_get_type ())
+#define UM_STRENGTH_BAR(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_STRENGTH_BAR, \
+ UmStrengthBar))
+#define UM_STRENGTH_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_STRENGTH_BAR, \
+ UmStrengthBarClass))
+#define UM_IS_STRENGTH_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_STRENGTH_BAR))
+#define UM_IS_STRENGTH_BAR_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_STRENGTH_BAR))
+#define UM_STRENGTH_BAR_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_STRENGTH_BAR, \
+ UmStrengthBarClass))
+
+typedef struct _UmStrengthBarClass UmStrengthBarClass;
+typedef struct _UmStrengthBar UmStrengthBar;
+typedef struct _UmStrengthBarPrivate UmStrengthBarPrivate;
+
+struct _UmStrengthBarClass {
+ GtkWidgetClass parent_class;
+};
+
+struct _UmStrengthBar {
+ GtkWidget parent_instance;
+ UmStrengthBarPrivate *priv;
+};
+
+GType um_strength_bar_get_type (void) G_GNUC_CONST;
+
+GtkWidget *um_strength_bar_new (void);
+void um_strength_bar_set_strength (UmStrengthBar *bar,
+ gdouble strength);
+gdouble um_strength_bar_get_strength (UmStrengthBar *bar);
+
+G_END_DECLS
+
+#endif /* _UM_STRENGTH_BAR_H_ */
diff --git a/panels/user-accounts/um-user-manager.c b/panels/user-accounts/um-user-manager.c
new file mode 100644
index 000000000..1e3901828
--- /dev/null
+++ b/panels/user-accounts/um-user-manager.c
@@ -0,0 +1,624 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_PATHS_H
+#include
+#endif /* HAVE_PATHS_H */
+
+#include
+#include
+#include
+#include
+#include
+
+#include "um-user-manager.h"
+
+enum {
+ USERS_LOADED,
+ USER_ADDED,
+ USER_REMOVED,
+ USER_CHANGED,
+ LAST_SIGNAL
+};
+
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static void um_user_manager_class_init (UmUserManagerClass *klass);
+static void um_user_manager_init (UmUserManager *user_manager);
+static void um_user_manager_finalize (GObject *object);
+
+static gpointer user_manager_object = NULL;
+
+G_DEFINE_TYPE (UmUserManager, um_user_manager, G_TYPE_OBJECT)
+
+static void
+um_user_manager_class_init (UmUserManagerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = um_user_manager_finalize;
+
+ signals [USERS_LOADED] =
+ g_signal_new ("users-loaded",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmUserManagerClass, users_loaded),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ signals [USER_ADDED] =
+ g_signal_new ("user-added",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmUserManagerClass, user_added),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, UM_TYPE_USER);
+ signals [USER_REMOVED] =
+ g_signal_new ("user-removed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmUserManagerClass, user_removed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, UM_TYPE_USER);
+ signals [USER_CHANGED] =
+ g_signal_new ("user-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ G_STRUCT_OFFSET (UmUserManagerClass, user_changed),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__OBJECT,
+ G_TYPE_NONE, 1, UM_TYPE_USER);
+}
+
+
+/* We maintain a ring for each group of users with the same real name.
+ * We need this to pick the right display names.
+ */
+static void
+remove_user_from_dupe_ring (UmUserManager *manager,
+ UmUser *user)
+{
+ GList *dupes;
+ UmUser *dup;
+
+ um_user_show_short_display_name (user);
+
+ dupes = g_object_get_data (G_OBJECT (user), "dupes");
+
+ if (dupes == NULL) {
+ return;
+ }
+
+ if (dupes->next == dupes->prev) {
+ dup = dupes->next->data;
+ um_user_show_short_display_name (dup);
+ g_signal_emit (manager, signals[USER_CHANGED], 0, dup);
+
+ g_list_free_1 (dupes->next);
+ g_object_set_data (G_OBJECT (dup), "dupes", NULL);
+ }
+ else {
+ dupes->next->prev = dupes->prev;
+ dupes->prev->next = dupes->next;
+ }
+
+ g_list_free_1 (dupes);
+ g_object_set_data (G_OBJECT (user), "dupes", NULL);
+}
+
+static gboolean
+match_real_name_hrfunc (gpointer key,
+ gpointer value,
+ gpointer user)
+{
+ return (value != user && g_strcmp0 (um_user_get_real_name (user), um_user_get_real_name (value)) == 0);
+}
+
+static void
+add_user_to_dupe_ring (UmUserManager *manager,
+ UmUser *user)
+{
+ UmUser *dup;
+ GList *dupes;
+ GList *l;
+
+ dup = g_hash_table_find (manager->user_by_object_path,
+ match_real_name_hrfunc, user);
+
+ if (!dup) {
+ return;
+ }
+
+ um_user_show_full_display_name (user);
+
+ dupes = g_object_get_data (G_OBJECT (dup), "dupes");
+ if (!dupes) {
+ um_user_show_full_display_name (dup);
+ g_signal_emit (manager, signals[USER_CHANGED], 0, dup);
+ dupes = g_list_append (NULL, dup);
+ g_object_set_data (G_OBJECT (dup), "dupes", dupes);
+ dupes->next = dupes->prev = dupes;
+ }
+
+ l = g_list_append (NULL, user);
+ g_object_set_data (G_OBJECT (user), "dupes", l);
+ l->prev = dupes->prev;
+ dupes->prev->next = l;
+ l->next = dupes;
+ dupes->prev = l;
+}
+
+static void
+user_changed_handler (UmUser *user,
+ UmUserManager *manager)
+{
+ remove_user_from_dupe_ring (manager, user);
+ add_user_to_dupe_ring (manager, user);
+ g_signal_emit (manager, signals[USER_CHANGED], 0, user);
+}
+
+static void
+user_added_handler (DBusGProxy *proxy,
+ const char *object_path,
+ gpointer user_data)
+{
+ UmUserManager *manager = UM_USER_MANAGER (user_data);
+ UmUser *user;
+
+ if (g_hash_table_lookup (manager->user_by_object_path, object_path))
+ return;
+
+ user = um_user_new_from_object_path (object_path);
+ if (!user)
+ return;
+
+ add_user_to_dupe_ring (manager, user);
+
+ g_signal_connect (user, "changed",
+ G_CALLBACK (user_changed_handler), manager);
+ g_hash_table_insert (manager->user_by_object_path, g_strdup (um_user_get_object_path (user)), g_object_ref (user));
+ g_hash_table_insert (manager->user_by_name, g_strdup (um_user_get_user_name (user)), g_object_ref (user));
+
+ g_signal_emit (manager, signals[USER_ADDED], 0, user);
+ g_object_unref (user);
+}
+
+static void
+user_deleted_handler (DBusGProxy *proxy,
+ const char *object_path,
+ gpointer user_data)
+{
+ UmUserManager *manager = UM_USER_MANAGER (user_data);
+ UmUser *user;
+
+ user = g_hash_table_lookup (manager->user_by_object_path, object_path);
+ g_object_ref (user);
+ g_signal_handlers_disconnect_by_func (user, user_changed_handler, manager);
+
+ remove_user_from_dupe_ring (manager, user);
+
+ g_hash_table_remove (manager->user_by_object_path, um_user_get_object_path (user));
+ g_hash_table_remove (manager->user_by_name, um_user_get_user_name (user));
+ g_signal_emit (manager, signals[USER_REMOVED], 0, user);
+ g_object_unref (user);
+}
+
+static void
+add_user (const gchar *object_path,
+ UmUserManager *manager)
+{
+ user_added_handler (NULL, object_path, manager);
+}
+
+static void
+got_users (DBusGProxy *proxy,
+ DBusGProxyCall *call_id,
+ gpointer data)
+{
+ UmUserManager *manager = data;
+ GError *error = NULL;
+ GPtrArray *paths;
+
+ if (!dbus_g_proxy_end_call (proxy,
+ call_id,
+ &error,
+ dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &paths,
+ G_TYPE_INVALID)) {
+ manager->no_service = TRUE;
+ g_error_free (error);
+ goto done;
+ }
+
+ g_ptr_array_foreach (paths, (GFunc)add_user, manager);
+
+ g_ptr_array_foreach (paths, (GFunc)g_free, NULL);
+ g_ptr_array_free (paths, TRUE);
+
+ done:
+ g_signal_emit (G_OBJECT (manager), signals[USERS_LOADED], 0);
+}
+
+static void
+get_users (UmUserManager *manager)
+{
+ g_debug ("calling 'ListCachedUsers'");
+ dbus_g_proxy_begin_call (manager->proxy,
+ "ListCachedUsers",
+ got_users,
+ manager,
+ NULL,
+ G_TYPE_INVALID);
+}
+
+static void
+um_user_manager_init (UmUserManager *manager)
+{
+ GError *error = NULL;
+
+ manager->user_by_object_path = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_object_unref);
+ manager->user_by_name = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_object_unref);
+
+ manager->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (manager->bus == NULL) {
+ g_warning ("Couldn't connect to system bus: %s", error->message);
+ g_error_free (error);
+ goto error;
+ }
+
+ manager->proxy = dbus_g_proxy_new_for_name (manager->bus,
+ "org.freedesktop.Accounts",
+ "/org/freedesktop/Accounts",
+ "org.freedesktop.Accounts");
+
+ dbus_g_proxy_add_signal (manager->proxy, "UserAdded", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+ dbus_g_proxy_add_signal (manager->proxy, "UserDeleted", DBUS_TYPE_G_OBJECT_PATH, G_TYPE_INVALID);
+
+ dbus_g_proxy_connect_signal (manager->proxy, "UserAdded",
+ G_CALLBACK (user_added_handler), manager, NULL);
+ dbus_g_proxy_connect_signal (manager->proxy, "UserDeleted",
+ G_CALLBACK (user_deleted_handler), manager, NULL);
+
+ get_users (manager);
+
+ error: ;
+}
+
+static void
+clear_dup (gpointer key,
+ gpointer value,
+ gpointer data)
+{
+ GList *dupes;
+
+ /* don't bother maintaining the ring, we're destroying the
+ * entire hash table anyway
+ */
+ dupes = g_object_get_data (G_OBJECT (value), "dupes");
+
+ if (dupes) {
+ g_list_free_1 (dupes);
+ g_object_set_data (G_OBJECT (value), "dupes", NULL);
+ }
+}
+
+static void
+um_user_manager_finalize (GObject *object)
+{
+ UmUserManager *manager;
+
+ manager = UM_USER_MANAGER (object);
+
+ g_hash_table_foreach (manager->user_by_object_path, clear_dup, NULL);
+ g_hash_table_destroy (manager->user_by_object_path);
+ g_hash_table_destroy (manager->user_by_name);
+
+ G_OBJECT_CLASS (um_user_manager_parent_class)->finalize (object);
+}
+
+UmUserManager *
+um_user_manager_ref_default (void)
+{
+ if (user_manager_object != NULL) {
+ g_object_ref (user_manager_object);
+ } else {
+ user_manager_object = g_object_new (UM_TYPE_USER_MANAGER, NULL);
+ g_object_add_weak_pointer (user_manager_object,
+ (gpointer *) &user_manager_object);
+ }
+
+ return UM_USER_MANAGER (user_manager_object);
+}
+
+typedef struct {
+ UmUserManager *manager;
+ gchar *user_name;
+ GAsyncReadyCallback callback;
+ gpointer data;
+ GDestroyNotify destroy;
+} AsyncUserOpData;
+
+static void
+async_user_op_data_free (gpointer d)
+{
+ AsyncUserOpData *data = d;
+
+ g_object_unref (data->manager);
+
+ g_free (data->user_name);
+
+ if (data->destroy)
+ data->destroy (data->data);
+
+ g_free (data);
+}
+
+static void
+create_user_done (DBusGProxy *proxy,
+ DBusGProxyCall *call_id,
+ gpointer user_data)
+{
+ AsyncUserOpData *data = user_data;
+ gchar *path;
+ GError *error;
+ GSimpleAsyncResult *res;
+
+ res = g_simple_async_result_new (G_OBJECT (data->manager),
+ data->callback,
+ data->data,
+ um_user_manager_create_user);
+ error = NULL;
+ if (!dbus_g_proxy_end_call (proxy,
+ call_id,
+ &error,
+ DBUS_TYPE_G_OBJECT_PATH, &path,
+ G_TYPE_INVALID)) {
+ /* dbus-glib fail:
+ * We have to translate the errors manually here, since
+ * calling dbus_g_error_has_name on the error returned in
+ * um_user_manager_create_user_finish doesn't work.
+ */
+ if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.PermissionDenied")) {
+ g_simple_async_result_set_error (res,
+ UM_USER_MANAGER_ERROR,
+ UM_USER_MANAGER_ERROR_PERMISSION_DENIED,
+ "Not authorized");
+ }
+ else if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.UserExists")) {
+ g_simple_async_result_set_error (res,
+ UM_USER_MANAGER_ERROR,
+ UM_USER_MANAGER_ERROR_USER_EXISTS,
+ _("A user with name '%s' already exists."),
+ data->user_name);
+ }
+ else {
+ g_simple_async_result_set_from_error (res, error);
+ }
+ g_error_free (error);
+ }
+ else {
+ g_simple_async_result_set_op_res_gpointer (res, path, g_free);
+ }
+
+ data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data);
+}
+
+gboolean
+um_user_manager_create_user_finish (UmUserManager *manager,
+ GAsyncResult *result,
+ UmUser **user,
+ GError **error)
+{
+ gchar *path;
+ GSimpleAsyncResult *res;
+
+ res = G_SIMPLE_ASYNC_RESULT (result);
+
+ *user = NULL;
+
+ if (g_simple_async_result_propagate_error (res, error)) {
+ return FALSE;
+ }
+
+ path = g_simple_async_result_get_op_res_gpointer (res);
+ *user = g_hash_table_lookup (manager->user_by_object_path, path);
+
+ return TRUE;
+}
+
+void
+um_user_manager_create_user (UmUserManager *manager,
+ const char *user_name,
+ const char *real_name,
+ gint account_type,
+ GAsyncReadyCallback done,
+ gpointer done_data,
+ GDestroyNotify destroy)
+{
+ AsyncUserOpData *data;
+
+ data = g_new0 (AsyncUserOpData, 1);
+ data->manager = g_object_ref (manager);
+ data->user_name = g_strdup (user_name);
+ data->callback = done;
+ data->data = done_data;
+ data->destroy = destroy;
+
+ dbus_g_proxy_begin_call (manager->proxy,
+ "CreateUser",
+ create_user_done,
+ data,
+ async_user_op_data_free,
+ G_TYPE_STRING, user_name,
+ G_TYPE_STRING, real_name,
+ G_TYPE_INT, account_type,
+ G_TYPE_INVALID);
+}
+
+static void
+delete_user_done (DBusGProxy *proxy,
+ DBusGProxyCall *call_id,
+ gpointer user_data)
+{
+ AsyncUserOpData *data = user_data;
+ GError *error;
+ GSimpleAsyncResult *res;
+
+ res = g_simple_async_result_new (G_OBJECT (data->manager),
+ data->callback,
+ data->data,
+ um_user_manager_delete_user);
+ error = NULL;
+ if (!dbus_g_proxy_end_call (proxy,
+ call_id,
+ &error,
+ G_TYPE_INVALID)) {
+ if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.PermissionDenied")) {
+ g_simple_async_result_set_error (res,
+ UM_USER_MANAGER_ERROR,
+ UM_USER_MANAGER_ERROR_PERMISSION_DENIED,
+ "Not authorized");
+ }
+ else if (dbus_g_error_has_name (error, "org.freedesktop.Accounts.Error.UserDoesntExists")) {
+ g_simple_async_result_set_error (res,
+ UM_USER_MANAGER_ERROR,
+ UM_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST,
+ _("This user does not exist."));
+ }
+ else {
+ g_simple_async_result_set_from_error (res, error);
+ g_error_free (error);
+ }
+ }
+
+ data->callback (G_OBJECT (data->manager), G_ASYNC_RESULT (res), data->data);
+}
+
+gboolean
+um_user_manager_delete_user_finish (UmUserManager *manager,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *res;
+
+ res = G_SIMPLE_ASYNC_RESULT (result);
+
+ if (g_simple_async_result_propagate_error (res, error)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void
+um_user_manager_delete_user (UmUserManager *manager,
+ UmUser *user,
+ gboolean remove_files,
+ GAsyncReadyCallback done,
+ gpointer done_data,
+ GDestroyNotify destroy)
+{
+ AsyncUserOpData *data;
+
+ data = g_new0 (AsyncUserOpData, 1);
+ data->manager = g_object_ref (manager);
+ data->callback = done;
+ data->data = done_data;
+ data->destroy = destroy;
+
+ dbus_g_proxy_begin_call (manager->proxy,
+ "DeleteUser",
+ delete_user_done,
+ data,
+ async_user_op_data_free,
+ G_TYPE_INT64, um_user_get_uid (user),
+ G_TYPE_BOOLEAN, remove_files,
+ G_TYPE_INVALID);
+}
+
+GSList *
+um_user_manager_list_users (UmUserManager *manager)
+{
+ GSList *list = NULL;
+ GHashTableIter iter;
+ gpointer value;
+
+ g_hash_table_iter_init (&iter, manager->user_by_name);
+ while (g_hash_table_iter_next (&iter, NULL, &value)) {
+ list = g_slist_prepend (list, value);
+ }
+
+ return g_slist_sort (list, (GCompareFunc) um_user_collate);
+}
+
+UmUser *
+um_user_manager_get_user (UmUserManager *manager,
+ const gchar *name)
+{
+ return g_hash_table_lookup (manager->user_by_name, name);
+}
+
+UmUser *
+um_user_manager_get_user_by_id (UmUserManager *manager,
+ uid_t uid)
+{
+ struct passwd *pwent;
+
+ pwent = getpwuid (uid);
+ if (!pwent) {
+ return NULL;
+ }
+
+ return um_user_manager_get_user (manager, pwent->pw_name);
+}
+
+gboolean
+um_user_manager_no_service (UmUserManager *manager)
+{
+ return manager->no_service;
+}
+
+GQuark
+um_user_manager_error_quark (void)
+{
+ return g_quark_from_static_string ("um-user-manager-error-quark");
+}
diff --git a/panels/user-accounts/um-user-manager.h b/panels/user-accounts/um-user-manager.h
new file mode 100644
index 000000000..b051a884a
--- /dev/null
+++ b/panels/user-accounts/um-user-manager.h
@@ -0,0 +1,112 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 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 3 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.
+ *
+ */
+
+#ifndef __UM_USER_MANAGER__
+#define __UM_USER_MANAGER__
+
+#include
+#include
+#include
+
+#include "um-user.h"
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_USER_MANAGER (um_user_manager_get_type ())
+#define UM_USER_MANAGER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), UM_TYPE_USER_MANAGER, UmUserManager))
+#define UM_USER_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), UM_TYPE_USER_MANAGER, UmUserManagerClass))
+#define UM_IS_USER_MANAGER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), UM_TYPE_USER_MANAGER))
+#define UM_IS_USER_MANAGER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), UM_TYPE_USER_MANAGER))
+#define UM_USER_MANAGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), UM_TYPE_USER_MANAGER, UmUserManagerClass))
+
+typedef struct
+{
+ GObject parent;
+
+ DBusGConnection *bus;
+ DBusGProxy *proxy;
+
+ GHashTable *user_by_object_path;
+ GHashTable *user_by_name;
+
+ gboolean no_service;
+} UmUserManager;
+
+typedef struct
+{
+ GObjectClass parent_class;
+
+ void (* users_loaded) (UmUserManager *user_managaer);
+ void (* user_added) (UmUserManager *user_manager,
+ UmUser *user);
+ void (* user_removed) (UmUserManager *user_manager,
+ UmUser *user);
+ void (* user_changed) (UmUserManager *user_manager,
+ UmUser *user);
+} UmUserManagerClass;
+
+
+typedef enum {
+ UM_USER_MANAGER_ERROR_FAILED,
+ UM_USER_MANAGER_ERROR_USER_EXISTS,
+ UM_USER_MANAGER_ERROR_USER_DOES_NOT_EXIST,
+ UM_USER_MANAGER_ERROR_PERMISSION_DENIED
+} UmUserManagerError;
+
+#define UM_USER_MANAGER_ERROR um_user_manager_error_quark ()
+
+GQuark um_user_manager_error_quark (void);
+
+GType um_user_manager_get_type (void);
+
+UmUserManager * um_user_manager_ref_default (void);
+
+gboolean um_user_manager_no_service (UmUserManager *manager);
+
+GSList * um_user_manager_list_users (UmUserManager *manager);
+UmUser * um_user_manager_get_user (UmUserManager *manager,
+ const char *user_name);
+UmUser * um_user_manager_get_user_by_id (UmUserManager *manager,
+ uid_t uid);
+
+void um_user_manager_create_user (UmUserManager *manager,
+ const char *user_name,
+ const char *real_name,
+ gint account_type,
+ GAsyncReadyCallback done,
+ gpointer user_data,
+ GDestroyNotify destroy);
+gboolean um_user_manager_create_user_finish (UmUserManager *manager,
+ GAsyncResult *result,
+ UmUser **user,
+ GError **error);
+void um_user_manager_delete_user (UmUserManager *manager,
+ UmUser *user,
+ gboolean remove_files,
+ GAsyncReadyCallback done,
+ gpointer user_data,
+ GDestroyNotify destroy);
+gboolean um_user_manager_delete_user_finish (UmUserManager *manager,
+ GAsyncResult *result,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __UM_USER_MANAGER__ */
diff --git a/panels/user-accounts/um-user-module.c b/panels/user-accounts/um-user-module.c
new file mode 100644
index 000000000..d96ed4081
--- /dev/null
+++ b/panels/user-accounts/um-user-module.c
@@ -0,0 +1,41 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include
+
+#include "um-user-panel.h"
+
+#include
+
+void
+g_io_module_load (GIOModule *module)
+{
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ /* register the panel */
+ um_user_panel_register (module);
+}
+
+void
+g_io_module_unload (GIOModule *module)
+{
+}
diff --git a/panels/user-accounts/um-user-panel.c b/panels/user-accounts/um-user-panel.c
new file mode 100644
index 000000000..4bb3d6eeb
--- /dev/null
+++ b/panels/user-accounts/um-user-panel.c
@@ -0,0 +1,1286 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include "um-user-panel.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#ifdef HAVE_CHEESE
+#include
+#endif /* HAVE_CHEESE */
+
+#include "marshal.h"
+
+#include "um-user.h"
+#include "um-user-manager.h"
+
+#include "um-strength-bar.h"
+#include "um-editable-button.h"
+#include "um-editable-entry.h"
+#include "um-editable-combo.h"
+#include "um-lockbutton.h"
+
+#include "um-account-dialog.h"
+#include "um-language-dialog.h"
+#include "um-login-options.h"
+#include "um-password-dialog.h"
+#include "um-photo-dialog.h"
+#include "um-fingerprint-dialog.h"
+#include "um-utils.h"
+#include "gdm-languages.h"
+
+G_DEFINE_DYNAMIC_TYPE (UmUserPanel, um_user_panel, CC_TYPE_PANEL)
+
+#define UM_USER_PANEL_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), UM_TYPE_USER_PANEL, UmUserPanelPrivate))
+
+struct _UmUserPanelPrivate {
+ UmUserManager *um;
+ GtkBuilder *builder;
+
+ GtkWidget *notebook;
+ GtkWidget *lock_button;
+ PolkitPermission *permission;
+ GtkWidget *language_chooser;
+
+ UmAccountDialog *account_dialog;
+ UmPasswordDialog *password_dialog;
+ UmPhotoDialog *photo_dialog;
+ UmLoginOptions *login_options;
+
+ PolkitAuthority *authority;
+};
+
+static GtkWidget *
+get_widget (UmUserPanelPrivate *d, const char *name)
+{
+ return (GtkWidget *)gtk_builder_get_object (d->builder, name);
+}
+
+enum {
+ USER_COL,
+ FACE_COL,
+ NAME_COL,
+ USER_ROW_COL,
+ TITLE_COL,
+ HEADING_ROW_COL,
+ SORT_KEY_COL,
+ NUM_USER_LIST_COLS
+};
+
+static UmUser *
+get_selected_user (UmUserPanelPrivate *d)
+{
+ GtkTreeView *tv;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ UmUser *user;
+
+ tv = (GtkTreeView *)get_widget (d, "list-treeview");
+ store = (GtkListStore *)gtk_tree_view_get_model (tv);
+ selection = gtk_tree_view_get_selection (tv);
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, USER_COL, &user, -1);
+ return user;
+ }
+
+ return NULL;
+}
+
+static void
+user_added (UmUserManager *um, UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkWidget *widget;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeIter iter;
+ GtkTreeIter dummy;
+ GdkPixbuf *pixbuf;
+ gchar *text;
+ GtkTreeSelection *selection;
+ gint sort_key;
+
+ g_debug ("user added: %d %s\n", um_user_get_uid (user), um_user_get_real_name (user));
+ widget = get_widget (d, "list-treeview");
+ model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
+ store = GTK_LIST_STORE (model);
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
+
+ pixbuf = um_user_render_icon (user, TRUE, 48);
+ text = g_strdup_printf ("%s\n%s",
+ um_user_get_display_name (user),
+ um_account_type_get_name (um_user_get_account_type (user)));
+
+ if (um_user_get_uid (user) == getuid ()) {
+ sort_key = 1;
+ }
+ else {
+ sort_key = 3;
+ }
+ gtk_list_store_append (store, &iter);
+
+ gtk_list_store_set (store, &iter,
+ USER_COL, user,
+ FACE_COL, pixbuf,
+ NAME_COL, text,
+ USER_ROW_COL, TRUE,
+ TITLE_COL, NULL,
+ HEADING_ROW_COL, FALSE,
+ SORT_KEY_COL, sort_key,
+ -1);
+ g_object_unref (pixbuf);
+ g_free (text);
+
+ if (sort_key == 1 &&
+ !gtk_tree_selection_get_selected (selection, &model, &dummy)) {
+ gtk_tree_selection_select_iter (selection, &iter);
+ }
+}
+
+static void
+get_previous_user_row (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *prev)
+{
+ GtkTreePath *path;
+ UmUser *user;
+
+ path = gtk_tree_model_get_path (model, iter);
+ while (gtk_tree_path_prev (path)) {
+ gtk_tree_model_get_iter (model, prev, path);
+ gtk_tree_model_get (model, prev, USER_COL, &user, -1);
+ if (user) {
+ g_object_unref (user);
+ break;
+ }
+ }
+ gtk_tree_path_free (path);
+}
+
+static gboolean
+get_next_user_row (GtkTreeModel *model,
+ GtkTreeIter *iter,
+ GtkTreeIter *next)
+{
+ UmUser *user;
+
+ *next = *iter;
+ while (gtk_tree_model_iter_next (model, next)) {
+ gtk_tree_model_get (model, next, USER_COL, &user, -1);
+ if (user) {
+ g_object_unref (user);
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static void
+user_removed (UmUserManager *um, UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkTreeView *tv;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkListStore *store;
+ GtkTreeIter iter, next;
+ UmUser *u;
+
+ g_debug ("user removed: %s\n", um_user_get_user_name (user));
+ tv = (GtkTreeView *)get_widget (d, "list-treeview");
+ selection = gtk_tree_view_get_selection (tv);
+ model = gtk_tree_view_get_model (tv);
+ store = GTK_LIST_STORE (model);
+ if (gtk_tree_model_get_iter_first (model, &iter)) {
+ do {
+ gtk_tree_model_get (model, &iter, USER_COL, &u, -1);
+
+ if (u != NULL) {
+ if (um_user_get_uid (user) == um_user_get_uid (u)) {
+ if (!get_next_user_row (model, &iter, &next))
+ get_previous_user_row (model, &iter, &next);
+ gtk_list_store_remove (store, &iter);
+ gtk_tree_selection_select_iter (selection, &next);
+ g_object_unref (u);
+ break;
+ }
+ g_object_unref (u);
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+ }
+}
+
+static void show_user (UmUser *user, UmUserPanelPrivate *d);
+
+static void
+user_changed (UmUserManager *um, UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkTreeView *tv;
+ GtkTreeSelection *selection;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ UmUser *current;
+ GdkPixbuf *pixbuf;
+ char *text;
+
+ tv = (GtkTreeView *)get_widget (d, "list-treeview");
+ model = gtk_tree_view_get_model (tv);
+ selection = gtk_tree_view_get_selection (tv);
+
+ gtk_tree_model_get_iter_first (model, &iter);
+ do {
+ gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1);
+ if (current == user) {
+ pixbuf = um_user_render_icon (user, TRUE, 48);
+ text = g_strdup_printf ("%s\n%s",
+ um_user_get_display_name (user),
+ um_account_type_get_name (um_user_get_account_type (user)));
+
+ gtk_list_store_set (GTK_LIST_STORE (model), &iter,
+ USER_COL, user,
+ FACE_COL, pixbuf,
+ NAME_COL, text,
+ -1);
+ g_object_unref (pixbuf);
+ g_free (text);
+ g_object_unref (current);
+
+ break;
+ }
+ if (current)
+ g_object_unref (current);
+
+ } while (gtk_tree_model_iter_next (model, &iter));
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1);
+
+ if (current == user) {
+ show_user (user, d);
+ }
+ if (current)
+ g_object_unref (current);
+ }
+}
+
+static void
+select_created_user (UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkTreeView *tv;
+ GtkTreeModel *model;
+ GtkTreeSelection *selection;
+ GtkTreeIter iter;
+ UmUser *current;
+ GtkTreePath *path;
+
+ tv = (GtkTreeView *)get_widget (d, "list-treeview");
+ model = gtk_tree_view_get_model (tv);
+ selection = gtk_tree_view_get_selection (tv);
+
+ gtk_tree_model_get_iter_first (model, &iter);
+ do {
+ gtk_tree_model_get (model, &iter, USER_COL, ¤t, -1);
+ if (user == current) {
+ path = gtk_tree_model_get_path (model, &iter);
+ gtk_tree_view_scroll_to_cell (tv, path, NULL, FALSE, 0.0, 0.0);
+ gtk_tree_selection_select_path (selection, path);
+ gtk_tree_path_free (path);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (model, &iter));
+}
+
+static void
+add_user (GtkButton *button, UmUserPanelPrivate *d)
+{
+ um_account_dialog_show (d->account_dialog,
+ GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ (UserCreatedCallback)select_created_user, d);
+}
+
+static void
+delete_user_done (UmUserManager *manager,
+ GAsyncResult *res,
+ UmUserPanelPrivate *d)
+{
+ GError *error;
+
+ error = NULL;
+ if (!um_user_manager_delete_user_finish (manager, res, &error)) {
+ if (!g_error_matches (error, UM_USER_MANAGER_ERROR, UM_USER_MANAGER_ERROR_PERMISSION_DENIED)) {
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ _("Failed to delete user"));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ "%s", error->message);
+
+ g_signal_connect (G_OBJECT (dialog), "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ gtk_window_present (GTK_WINDOW (dialog));
+ }
+ g_error_free (error);
+ }
+}
+
+static void
+delete_user_response (GtkWidget *dialog,
+ gint response_id,
+ UmUserPanelPrivate *d)
+{
+ UmUser *user;
+ gboolean remove_files;
+
+ gtk_widget_destroy (dialog);
+
+ if (response_id == GTK_RESPONSE_CANCEL) {
+ return;
+ }
+ else if (response_id == GTK_RESPONSE_NO) {
+ remove_files = TRUE;
+ }
+ else {
+ remove_files = FALSE;
+ }
+
+ user = get_selected_user (d);
+
+ um_user_manager_delete_user (d->um,
+ user,
+ remove_files,
+ (GAsyncReadyCallback)delete_user_done,
+ d,
+ NULL);
+
+ g_object_unref (user);
+}
+
+static void
+delete_user (GtkButton *button, UmUserPanelPrivate *d)
+{
+ UmUser *user;
+ GtkWidget *dialog;
+
+ user = get_selected_user (d);
+ if (user == NULL) {
+ return;
+ }
+ else if (um_user_get_uid (user) == getuid ()) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ 0,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ _("You cannot delete your own account."));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ }
+ else if (um_user_is_logged_in (user)) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ 0,
+ GTK_MESSAGE_INFO,
+ GTK_BUTTONS_CLOSE,
+ _("%s is still logged in"),
+ um_user_get_real_name (user));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("Deleting a user while they are logged in can leave the system in an inconsistent state."));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+ }
+ else {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ 0,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ _("Do you want to keep %s's files?"),
+ um_user_get_real_name (user));
+
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("It is possible to keep the home directory, mail spool and temporary files around when deleting a user account."));
+
+ gtk_dialog_add_buttons (GTK_DIALOG (dialog),
+ _("_Delete Files"), GTK_RESPONSE_NO,
+ _("_Keep Files"), GTK_RESPONSE_YES,
+ _("_Cancel"), GTK_RESPONSE_CANCEL,
+ NULL);
+
+ gtk_window_set_icon_name (GTK_WINDOW (dialog), "system-users");
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (delete_user_response), d);
+ }
+
+ g_signal_connect (dialog, "close",
+ G_CALLBACK (gtk_widget_destroy), NULL);
+
+ gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
+
+ gtk_window_present (GTK_WINDOW (dialog));
+
+}
+
+static const gchar *
+get_password_mode_text (UmUser *user)
+{
+ const gchar *text;
+
+ if (um_user_get_locked (user)) {
+ text = C_("Password mode", "Account disabled");
+ }
+ else {
+ switch (um_user_get_password_mode (user)) {
+ case UM_PASSWORD_MODE_REGULAR:
+ /* five bullets */
+ text = "\xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2\xe2\x80\xa2";
+ break;
+ case UM_PASSWORD_MODE_SET_AT_LOGIN:
+ text = C_("Password mode", "To be set at next login");
+ break;
+ case UM_PASSWORD_MODE_NONE:
+ text = C_("Password mode", "None");
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+ }
+
+ return text;
+}
+
+static void
+show_user (UmUser *user, UmUserPanelPrivate *d)
+{
+ GtkWidget *image;
+ GtkWidget *label;
+ GtkWidget *label2;
+ GtkWidget *label3;
+ GdkPixbuf *pixbuf;
+ gchar *lang;
+ GtkWidget *widget;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ pixbuf = um_user_render_icon (user, FALSE, 48);
+ image = get_widget (d, "user-icon-image");
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+ image = get_widget (d, "user-icon-image2");
+ gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
+ g_object_unref (pixbuf);
+
+ um_photo_dialog_set_user (d->photo_dialog, user);
+
+ widget = get_widget (d, "full-name-entry");
+ um_editable_entry_set_text (UM_EDITABLE_ENTRY (widget), um_user_get_real_name (user));
+ gtk_widget_set_tooltip_text (widget, um_user_get_user_name (user));
+
+ widget = get_widget (d, "account-type-combo");
+ um_editable_combo_set_active (UM_EDITABLE_COMBO (widget), um_user_get_account_type (user));
+
+ widget = get_widget (d, "account-password-button");
+ um_editable_button_set_text (UM_EDITABLE_BUTTON (widget), get_password_mode_text (user));
+
+ widget = get_widget (d, "account-email-entry");
+ um_editable_entry_set_text (UM_EDITABLE_ENTRY (widget), um_user_get_email (user));
+
+ widget = get_widget (d, "account-language-combo");
+ model = um_editable_combo_get_model (UM_EDITABLE_COMBO (widget));
+ um_add_user_languages (model);
+
+ lang = g_strdup (um_user_get_language (user));
+ if (!lang)
+ lang = um_get_current_language ();
+ um_get_iter_for_language (model, lang, &iter);
+ um_editable_combo_set_active_iter (UM_EDITABLE_COMBO (widget), &iter);
+ g_free (lang);
+
+ label = get_widget (d, "account-location-entry");
+ um_editable_entry_set_text (UM_EDITABLE_ENTRY (label), um_user_get_location (user));
+
+ widget = get_widget (d, "account-fingerprint-notebook");
+ label = get_widget (d, "account-fingerprint-label");
+ label2 = get_widget (d, "account-fingerprint-value-label");
+ label3 = get_widget (d, "account-fingerprint-button-label");
+ if (um_user_get_uid (user) != getuid() ||
+ !set_fingerprint_label (label2, label3)) {
+ gtk_widget_hide (label);
+ gtk_widget_hide (widget);
+ } else {
+ gtk_widget_show (label);
+ gtk_widget_show (widget);
+ }
+}
+
+static void lockbutton_changed (UmLockButton *button, gpointer data);
+
+static void
+selected_user_changed (GtkTreeSelection *selection, UmUserPanelPrivate *d)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ UmUser *user;
+
+ if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+ gtk_tree_model_get (model, &iter, USER_COL, &user, -1);
+ show_user (user, d);
+ lockbutton_changed (UM_LOCK_BUTTON (d->lock_button), d);
+ g_object_unref (user);
+ }
+}
+
+static void
+change_name_done (GtkWidget *entry,
+ UmUserPanelPrivate *d)
+{
+ const gchar *text;
+ UmUser *user;
+
+ user = get_selected_user (d);
+
+ text = um_editable_entry_get_text (UM_EDITABLE_ENTRY (entry));
+
+ if (g_strcmp0 (text, um_user_get_location (user)) != 0) {
+ um_user_set_real_name (user, text);
+ }
+}
+
+static void
+account_type_changed (UmEditableCombo *combo,
+ UmUserPanelPrivate *d)
+{
+ UmUser *user;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gint account_type;
+
+ user = get_selected_user (d);
+ model = um_editable_combo_get_model (combo);
+ um_editable_combo_get_active_iter (combo, &iter);
+ gtk_tree_model_get (model, &iter, 1, &account_type, -1);
+
+ if (account_type != um_user_get_account_type (user)) {
+ um_user_set_account_type (user, account_type);
+ }
+}
+
+static void
+language_response (GtkDialog *dialog,
+ gint response_id,
+ UmUserPanelPrivate *d)
+{
+ GtkWidget *combo;
+ UmUser *user;
+ gchar *lang;
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+
+ user = get_selected_user (d);
+ combo = get_widget (d, "account-language-combo");
+ model = um_editable_combo_get_model (UM_EDITABLE_COMBO (combo));
+
+ if (response_id == GTK_RESPONSE_OK) {
+ lang = um_language_chooser_get_language (GTK_WIDGET (dialog));
+ um_user_set_language (user, lang);
+ }
+ else {
+ lang = g_strdup (um_user_get_language (user));
+ if (!lang)
+ lang = um_get_current_language ();
+ }
+ um_get_iter_for_language (model, lang, &iter);
+ um_editable_combo_set_active_iter (UM_EDITABLE_COMBO (combo), &iter);
+ g_free (lang);
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ gtk_widget_set_sensitive (combo, TRUE);
+}
+
+static gboolean
+finish_language_chooser (UmUserPanelPrivate *d)
+{
+ GtkWidget *combo;
+
+ combo = get_widget (d, "account-language-combo");
+ d->language_chooser = um_language_chooser_new ();
+ gtk_window_set_transient_for (GTK_WINDOW (d->language_chooser),
+ GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)));
+ g_signal_connect (d->language_chooser, "response",
+ G_CALLBACK (language_response), d);
+ g_signal_connect (d->language_chooser, "delete-event",
+ G_CALLBACK (gtk_widget_hide_on_delete), NULL);
+
+ gdk_window_set_cursor (gtk_widget_get_window (gtk_widget_get_toplevel (d->notebook)), NULL);
+ gtk_window_present (GTK_WINDOW (d->language_chooser));
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
+
+ return FALSE;
+}
+
+static void
+language_changed (UmEditableCombo *combo,
+ UmUserPanelPrivate *d)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gchar *lang;
+ UmUser *user;
+ GdkCursor *cursor;
+
+ if (!um_editable_combo_get_active_iter (combo, &iter))
+ return;
+
+ user = get_selected_user (d);
+ model = um_editable_combo_get_model (combo);
+
+ gtk_tree_model_get (model, &iter, 0, &lang, -1);
+ if (lang) {
+ if (g_strcmp0 (lang, um_user_get_language (user)) != 0) {
+ um_user_set_language (user, lang);
+ }
+ g_free (lang);
+ return;
+ }
+
+ if (d->language_chooser) {
+ gtk_window_present (GTK_WINDOW (d->language_chooser));
+ gtk_widget_set_sensitive (GTK_WIDGET (combo), FALSE);
+ return;
+ }
+
+ cursor = gdk_cursor_new (GDK_WATCH);
+ gdk_window_set_cursor (gtk_widget_get_window (gtk_widget_get_toplevel (d->notebook)),
+ cursor);
+ gdk_cursor_unref (cursor);
+
+ g_idle_add ((GSourceFunc)finish_language_chooser, d);
+}
+
+static void
+change_password (GtkButton *button, UmUserPanelPrivate *d)
+{
+ UmUser *user;
+
+ user = get_selected_user (d);
+
+ um_password_dialog_set_user (d->password_dialog, user);
+ um_password_dialog_show (d->password_dialog,
+ GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)));
+
+ g_object_unref (user);
+}
+
+static void
+change_email_done (UmEditableEntry *e,
+ UmUserPanelPrivate *d)
+{
+ const gchar *text;
+ UmUser *user;
+
+ user = get_selected_user (d);
+
+ text = um_editable_entry_get_text (e);
+
+ if (g_strcmp0 (text, um_user_get_email (user)) != 0) {
+ um_user_set_email (user, text);
+ }
+}
+
+static void
+change_location_done (GtkWidget *entry,
+ UmUserPanelPrivate *d)
+{
+ const gchar *text;
+ UmUser *user;
+
+ user = get_selected_user (d);
+
+ text = um_editable_entry_get_text (UM_EDITABLE_ENTRY (entry));
+
+ if (g_strcmp0 (text, um_user_get_location (user)) != 0) {
+ um_user_set_location (user, text);
+ }
+}
+
+static void
+change_fingerprint (GtkButton *button, UmUserPanelPrivate *d)
+{
+ GtkWidget *label, *label2;
+ UmUser *user;
+
+ user = get_selected_user (d);
+ g_assert (g_strcmp0 (g_get_user_name (), um_user_get_user_name (user)) == 0);
+
+ label = get_widget (d, "account-fingerprint-value-label");
+ label2 = get_widget (d, "account-fingerprint-button-label");
+ fingerprint_button_clicked (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)), label, label2, user);
+ g_object_unref (user);
+}
+
+static gint
+sort_users (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer data)
+{
+ UmUser *ua, *ub;
+ gint sa, sb;
+ gint result;
+
+ gtk_tree_model_get (model, a, USER_COL, &ua, SORT_KEY_COL, &sa, -1);
+ gtk_tree_model_get (model, b, USER_COL, &ub, SORT_KEY_COL, &sb, -1);
+
+ if (sa < sb) {
+ result = -1;
+ }
+ else if (sa > sb) {
+ result = 1;
+ }
+ else {
+ result = um_user_collate (ua, ub);
+ }
+
+ if (ua) {
+ g_object_unref (ua);
+ }
+ if (ub) {
+ g_object_unref (ub);
+ }
+
+ return result;
+}
+
+static gboolean
+dont_select_headings (GtkTreeSelection *selection,
+ GtkTreeModel *model,
+ GtkTreePath *path,
+ gboolean selected,
+ gpointer data)
+{
+ GtkTreeIter iter;
+ gboolean is_user;
+
+ gtk_tree_model_get_iter (model, &iter, path);
+ gtk_tree_model_get (model, &iter, USER_ROW_COL, &is_user, -1);
+
+ return is_user;
+}
+
+static void
+users_loaded (UmUserManager *manager,
+ UmUserPanelPrivate *d)
+{
+ GSList *list, *l;
+ UmUser *user;
+ GtkWidget *dialog;
+
+ if (um_user_manager_no_service (d->um)) {
+ dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (d->notebook)),
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_OTHER,
+ GTK_BUTTONS_CLOSE,
+ _("Failed to contact the accounts service"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
+ _("Please make sure that the AccountService is installed and enabled."));
+ g_signal_connect (dialog, "response",
+ G_CALLBACK (gtk_main_quit), NULL);
+ gtk_widget_show (dialog);
+ }
+
+ list = um_user_manager_list_users (d->um);
+ g_debug ("Got %d users\n", g_slist_length (list));
+
+ g_signal_connect (d->um, "user-changed", G_CALLBACK (user_changed), d);
+
+ for (l = list; l; l = l->next) {
+ user = l->data;
+ g_debug ("adding user %s\n", um_user_get_real_name (user));
+ user_added (d->um, user, d);
+ }
+ g_slist_free (list);
+
+ g_signal_connect (d->um, "user-added", G_CALLBACK (user_added), d);
+ g_signal_connect (d->um, "user-removed", G_CALLBACK (user_removed), d);
+}
+
+static void
+add_unlock_tooltip (GtkWidget *button)
+{
+ const gchar *names[3];
+ GIcon *icon;
+
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ setup_tooltip_with_embedded_icon (button,
+ _("To make changes,\nclick the * icon first"),
+ "*",
+ icon);
+ g_object_unref (icon);
+ g_signal_connect (button, "button-release-event",
+ G_CALLBACK (show_tooltip_now), NULL);
+}
+
+static void
+remove_unlock_tooltip (GtkWidget *button)
+{
+ setup_tooltip_with_embedded_icon (button, NULL, NULL, NULL);
+ g_signal_handlers_disconnect_by_func (button,
+ G_CALLBACK (show_tooltip_now), NULL);
+}
+
+static void
+lockbutton_changed (UmLockButton *button,
+ gpointer data)
+{
+ UmUserPanelPrivate *d = data;
+ gboolean is_authorized;
+ gboolean self_selected;
+ UmUser *user;
+ GtkWidget *widget;
+
+ user = get_selected_user (d);
+ if (!user) {
+ return;
+ }
+
+ is_authorized = g_permission_get_allowed (G_PERMISSION (d->permission));
+ self_selected = um_user_get_uid (user) == geteuid ();
+
+ widget = get_widget (d, "add-user-button");
+ gtk_widget_set_sensitive (widget, is_authorized);
+ if (is_authorized) {
+ setup_tooltip_with_embedded_icon (widget, _("Create a user"), NULL, NULL);
+ }
+ else {
+ const gchar *names[3];
+ GIcon *icon;
+
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ setup_tooltip_with_embedded_icon (widget,
+ _("To create a user,\nclick the * icon first"),
+ "*",
+ icon);
+ g_object_unref (icon);
+ }
+
+ widget = get_widget (d, "delete-user-button");
+ gtk_widget_set_sensitive (widget, is_authorized && !self_selected);
+ if (is_authorized) {
+ setup_tooltip_with_embedded_icon (widget, _("Delete the selected user"), NULL, NULL);
+ }
+ else {
+ const gchar *names[3];
+ GIcon *icon;
+
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+
+ setup_tooltip_with_embedded_icon (widget,
+ _("To delete the selected user,\nclick the * icon first"),
+ "*",
+ icon);
+ g_object_unref (icon);
+ }
+
+ if (is_authorized) {
+ um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-type-combo")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-type-combo"));
+ }
+ else {
+ um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-type-combo")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-type-combo"));
+ }
+
+ if (is_authorized || self_selected) {
+ gtk_widget_show (get_widget (d, "user-icon-button"));
+ gtk_widget_hide (get_widget (d, "user-icon-nonbutton"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "full-name-entry")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "full-name-entry"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "account-email-entry")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-email-entry"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "account-location-entry")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-location-entry"));
+
+ um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-language-combo")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-language-combo"));
+
+ um_editable_button_set_editable (UM_EDITABLE_BUTTON (get_widget (d, "account-password-button")), TRUE);
+ remove_unlock_tooltip (get_widget (d, "account-password-button"));
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (get_widget (d, "account-fingerprint-notebook")), 1);
+ }
+ else {
+ gtk_widget_hide (get_widget (d, "user-icon-button"));
+ gtk_widget_show (get_widget (d, "user-icon-nonbutton"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "full-name-entry")), FALSE);
+ add_unlock_tooltip (get_widget (d, "full-name-entry"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "account-email-entry")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-email-entry"));
+
+ um_editable_entry_set_editable (UM_EDITABLE_ENTRY (get_widget (d, "account-location-entry")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-location-entry"));
+
+ um_editable_combo_set_editable (UM_EDITABLE_COMBO (get_widget (d, "account-language-combo")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-language-combo"));
+
+ um_editable_button_set_editable (UM_EDITABLE_BUTTON (get_widget (d, "account-password-button")), FALSE);
+ add_unlock_tooltip (get_widget (d, "account-password-button"));
+
+ gtk_notebook_set_current_page (GTK_NOTEBOOK (get_widget (d, "account-fingerprint-notebook")), 0);
+ }
+
+ um_password_dialog_set_privileged (d->password_dialog, is_authorized);
+}
+
+static gboolean
+match_user (GtkTreeModel *model,
+ gint column,
+ const gchar *key,
+ GtkTreeIter *iter,
+ gpointer search_data)
+{
+ UmUser *user;
+ const gchar *name;
+ gchar *normalized_key = NULL;
+ gchar *normalized_name = NULL;
+ gchar *case_normalized_key = NULL;
+ gchar *case_normalized_name = NULL;
+ gchar *p;
+ gboolean result = TRUE;
+ gint i;
+
+ gtk_tree_model_get (model, iter, USER_COL, &user, -1);
+
+ if (!user) {
+ goto out;
+ }
+
+ normalized_key = g_utf8_normalize (key, -1, G_NORMALIZE_ALL);
+ if (!normalized_key) {
+ goto out;
+ }
+
+ case_normalized_key = g_utf8_casefold (normalized_key, -1);
+
+ for (i = 0; i < 2; i++) {
+ if (i == 0) {
+ name = um_user_get_real_name (user);
+ }
+ else {
+ name = um_user_get_user_name (user);
+ }
+ g_free (normalized_name);
+ normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_ALL);
+ if (normalized_name) {
+ g_free (case_normalized_name);
+ case_normalized_name = g_utf8_casefold (normalized_name, -1);
+ p = strstr (case_normalized_name, case_normalized_key);
+
+ /* poor man's \b */
+ if (p == case_normalized_name || (p && p[-1] == ' ')) {
+ result = FALSE;
+ break;
+ }
+ }
+ }
+
+ out:
+ if (user) {
+ g_object_unref (user);
+ }
+ g_free (normalized_key);
+ g_free (case_normalized_key);
+ g_free (normalized_name);
+ g_free (case_normalized_name);
+
+ return result;
+}
+
+static void
+setup_main_window (UmUserPanelPrivate *d)
+{
+ GtkWidget *userlist;
+ GtkTreeModel *model;
+ GtkListStore *store;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *cell;
+ GtkTreeSelection *selection;
+ GtkWidget *button;
+ GtkTreeIter iter;
+ gint expander_size;
+ GtkWidget *box;
+ gchar *title;
+ GIcon *icon;
+ const gchar *names[3];
+
+ userlist = get_widget (d, "list-treeview");
+ store = gtk_list_store_new (NUM_USER_LIST_COLS,
+ UM_TYPE_USER,
+ GDK_TYPE_PIXBUF,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ G_TYPE_STRING,
+ G_TYPE_BOOLEAN,
+ G_TYPE_INT);
+ model = (GtkTreeModel *)store;
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (model), sort_users, NULL, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID, GTK_SORT_ASCENDING);
+ gtk_tree_view_set_model (GTK_TREE_VIEW (userlist), model);
+ gtk_tree_view_set_search_column (GTK_TREE_VIEW (userlist), USER_COL);
+ gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (userlist),
+ match_user, NULL, NULL);
+
+ g_signal_connect (d->um, "users-loaded", G_CALLBACK (users_loaded), d);
+
+ gtk_widget_style_get (userlist, "expander-size", &expander_size, NULL);
+ gtk_tree_view_set_level_indentation (GTK_TREE_VIEW (userlist), - (expander_size + 6));
+
+ title = g_strdup_printf ("%s", _("My Account"));
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ TITLE_COL, title,
+ HEADING_ROW_COL, TRUE,
+ SORT_KEY_COL, 0,
+ -1);
+ g_free (title);
+
+ title = g_strdup_printf ("%s", _("Other Accounts"));
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ TITLE_COL, title,
+ HEADING_ROW_COL, TRUE,
+ SORT_KEY_COL, 2,
+ -1);
+ g_free (title);
+
+ column = gtk_tree_view_column_new ();
+ cell = gtk_cell_renderer_pixbuf_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, FALSE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "pixbuf", FACE_COL);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", USER_ROW_COL);
+ cell = gtk_cell_renderer_text_new ();
+ g_object_set (cell, "ellipsize", PANGO_ELLIPSIZE_END, "width-chars", 18, NULL);
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "markup", NAME_COL);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", USER_ROW_COL);
+ cell = gtk_cell_renderer_text_new ();
+ gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (column), cell, TRUE);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "markup", TITLE_COL);
+ gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (column), cell, "visible", HEADING_ROW_COL);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (userlist), column);
+
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (userlist));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+ g_signal_connect (selection, "changed", G_CALLBACK (selected_user_changed), d);
+ gtk_tree_selection_set_select_function (selection, dont_select_headings, NULL, NULL);
+
+ button = get_widget (d, "add-user-button");
+ g_signal_connect (button, "clicked", G_CALLBACK (add_user), d);
+
+ button = get_widget (d, "delete-user-button");
+ g_signal_connect (button, "clicked", G_CALLBACK (delete_user), d);
+
+ button = get_widget (d, "user-icon-nonbutton");
+ add_unlock_tooltip (button);
+
+ button = get_widget (d, "full-name-entry");
+ g_signal_connect (button, "editing-done", G_CALLBACK (change_name_done), d);
+
+ button = get_widget (d, "account-type-combo");
+ g_signal_connect (button, "editing-done", G_CALLBACK (account_type_changed), d);
+
+ button = get_widget (d, "account-password-button");
+ g_signal_connect (button, "start-editing", G_CALLBACK (change_password), d);
+
+ button = get_widget (d, "account-email-entry");
+ g_signal_connect (button, "editing-done", G_CALLBACK (change_email_done), d);
+
+ button = get_widget (d, "account-language-combo");
+ g_signal_connect (button, "editing-done", G_CALLBACK (language_changed), d);
+
+ button = get_widget (d, "account-location-entry");
+ g_signal_connect (button, "editing-done", G_CALLBACK (change_location_done), d);
+
+ button = get_widget (d, "account-fingerprint-button");
+ g_signal_connect (button, "clicked",
+ G_CALLBACK (change_fingerprint), d);
+
+ d->permission = polkit_permission_new_sync ("org.freedesktop.accounts.user-administration", NULL, NULL, NULL);
+ button = um_lock_button_new (d->permission);
+ gtk_widget_set_margin_top (button, 12);
+ gtk_widget_show (button);
+ box = get_widget (d, "userlist-vbox");
+ gtk_box_pack_end (GTK_BOX (box), button, FALSE, FALSE, 0);
+ g_signal_connect (button, "changed",
+ G_CALLBACK (lockbutton_changed), d);
+ lockbutton_changed (UM_LOCK_BUTTON (button), d);
+ d->lock_button = button;
+
+ button = get_widget (d, "add-user-button");
+ names[0] = "changes-prevent-symbolic";
+ names[1] = "changes-prevent";
+ names[2] = NULL;
+ icon = g_themed_icon_new_from_names (names, -1);
+ setup_tooltip_with_embedded_icon (button,
+ _("To create a user,\nclick the * icon first"),
+ "*",
+ icon);
+ button = get_widget (d, "delete-user-button");
+ setup_tooltip_with_embedded_icon (button,
+ _("To delete the selected user,\nclick the * icon first"),
+ "*",
+ icon);
+ g_object_unref (icon);
+}
+
+static void
+um_user_panel_init (UmUserPanel *self)
+{
+ UmUserPanelPrivate *d;
+ GError *error;
+ volatile GType type;
+ const gchar *filename;
+ GtkWidget *button;
+
+ dbus_g_object_register_marshaller (fprintd_marshal_VOID__STRING_BOOLEAN,
+ G_TYPE_NONE, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INVALID);
+
+ d = self->priv = UM_USER_PANEL_PRIVATE (self);
+
+ /* register types that the builder might need */
+ type = um_strength_bar_get_type ();
+ type = um_editable_button_get_type ();
+ type = um_editable_entry_get_type ();
+ type = um_editable_combo_get_type ();
+
+ d->builder = gtk_builder_new ();
+ d->um = um_user_manager_ref_default ();
+ d->authority = polkit_authority_get_sync (NULL, NULL);
+
+ filename = UIDIR "/user-accounts-dialog.ui";
+ if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
+ filename = "../data/user-accounts-dialog.ui";
+ }
+ error = NULL;
+ if (!gtk_builder_add_from_file (d->builder, filename, &error)) {
+ g_error ("%s", error->message);
+ g_error_free (error);
+ exit (1);
+ }
+
+ setup_main_window (d);
+ d->login_options = um_login_options_new (d->builder);
+ d->account_dialog = um_account_dialog_new ();
+ d->password_dialog = um_password_dialog_new ();
+ button = get_widget (d, "user-icon-button");
+ d->photo_dialog = um_photo_dialog_new (button);
+ d->notebook = get_widget (d, "top-level-notebook");
+ gtk_widget_reparent (d->notebook, GTK_WIDGET (self));
+}
+
+static void
+um_user_panel_dispose (GObject *object)
+{
+ UmUserPanelPrivate *priv = UM_USER_PANEL (object)->priv;
+
+ if (priv->um) {
+ g_object_unref (priv->um);
+ priv->um = NULL;
+ }
+ if (priv->builder) {
+ g_object_unref (priv->builder);
+ priv->builder = NULL;
+ }
+ if (priv->account_dialog) {
+ um_account_dialog_free (priv->account_dialog);
+ priv->account_dialog = NULL;
+ }
+ if (priv->password_dialog) {
+ um_password_dialog_free (priv->password_dialog);
+ priv->password_dialog = NULL;
+ }
+ if (priv->photo_dialog) {
+ um_photo_dialog_free (priv->photo_dialog);
+ priv->photo_dialog = NULL;
+ }
+ if (priv->login_options) {
+ um_login_options_free (priv->login_options);
+ priv->login_options = NULL;
+ }
+ if (priv->authority) {
+ g_object_unref (priv->authority);
+ priv->authority = NULL;
+ }
+
+ G_OBJECT_CLASS (um_user_panel_parent_class)->dispose (object);
+}
+
+static void
+um_user_panel_class_init (UmUserPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = um_user_panel_dispose;
+
+ g_type_class_add_private (klass, sizeof (UmUserPanelPrivate));
+}
+
+static void
+um_user_panel_class_finalize (UmUserPanelClass *klass)
+{
+}
+
+void
+um_user_panel_register (GIOModule *module)
+{
+ um_user_panel_register_type (G_TYPE_MODULE (module));
+ g_io_extension_point_implement (CC_SHELL_PANEL_EXTENSION_POINT,
+ UM_TYPE_USER_PANEL, "user-accounts", 0);
+}
diff --git a/panels/user-accounts/um-user-panel.h b/panels/user-accounts/um-user-panel.h
new file mode 100644
index 000000000..4f5aec0fb
--- /dev/null
+++ b/panels/user-accounts/um-user-panel.h
@@ -0,0 +1,59 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef _UM_USER_PANEL_H
+#define _UM_USER_PANEL_H
+
+#include
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_USER_PANEL um_user_panel_get_type()
+
+#define UM_USER_PANEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), UM_TYPE_USER_PANEL, UmUserPanel))
+#define UM_USER_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), UM_TYPE_USER_PANEL, UmUserPanelClass))
+#define UM_IS_USER_PANEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), UM_TYPE_USER_PANEL))
+#define UM_IS_USER_PANEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), UM_TYPE_USER_PANEL))
+#define UM_USER_PANEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), UM_TYPE_USER_PANEL, UmUserPanelClass))
+
+typedef struct _UmUserPanel UmUserPanel;
+typedef struct _UmUserPanelClass UmUserPanelClass;
+typedef struct _UmUserPanelPrivate UmUserPanelPrivate;
+
+struct _UmUserPanel
+{
+ CcPanel parent;
+
+ UmUserPanelPrivate *priv;
+};
+
+struct _UmUserPanelClass
+{
+ CcPanelClass parent_class;
+};
+
+GType um_user_panel_get_type (void) G_GNUC_CONST;
+
+void um_user_panel_register (GIOModule *module);
+
+G_END_DECLS
+
+#endif /* _UM_USER_PANEL_H */
diff --git a/panels/user-accounts/um-user.c b/panels/user-accounts/um-user.c
new file mode 100644
index 000000000..ce92b4e12
--- /dev/null
+++ b/panels/user-accounts/um-user.c
@@ -0,0 +1,1093 @@
+/* -*- 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
+
+#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;
+} UserProperties;
+
+static void
+collect_props (const gchar *key,
+ const GValue *value,
+ UserProperties *props)
+{
+ gboolean handled = TRUE;
+
+ if (strcmp (key, "Uid") == 0) {
+ props->uid = g_value_get_uint64 (value);
+ }
+ else if (strcmp (key, "UserName") == 0) {
+ props->user_name = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "RealName") == 0) {
+ props->real_name = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "AccountType") == 0) {
+ props->account_type = g_value_get_int (value);
+ }
+ else if (strcmp (key, "Email") == 0) {
+ props->email = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "Language") == 0) {
+ props->language = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "Location") == 0) {
+ props->location = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "LoginFrequency") == 0) {
+ props->login_frequency = g_value_get_uint64 (value);
+ }
+ else if (strcmp (key, "IconFile") == 0) {
+ props->icon_file = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "Locked") == 0) {
+ props->locked = g_value_get_boolean (value);
+ }
+ else if (strcmp (key, "AutomaticLogin") == 0) {
+ props->automatic_login = g_value_get_boolean (value);
+ }
+ else if (strcmp (key, "PasswordMode") == 0) {
+ props->password_mode = g_value_get_int (value);
+ }
+ else if (strcmp (key, "PasswordHint") == 0) {
+ props->password_hint = g_value_dup_string (value);
+ }
+ else if (strcmp (key, "HomeDirectory") == 0) {
+ /* ignore */
+ }
+ else if (strcmp (key, "Shell") == 0) {
+ /* ignore */
+ }
+ else {
+ handled = FALSE;
+ }
+
+ if (!handled)
+ g_debug ("unhandled property %s", key);
+}
+
+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 (DBusGConnection *bus,
+ const gchar *object_path)
+{
+ UserProperties *props;
+ GError *error;
+ DBusGProxy *proxy;
+ GHashTable *hash_table;
+
+ props = g_new0 (UserProperties, 1);
+
+ proxy = dbus_g_proxy_new_for_name (bus,
+ "org.freedesktop.Accounts",
+ object_path,
+ "org.freedesktop.DBus.Properties");
+ error = NULL;
+ if (!dbus_g_proxy_call (proxy,
+ "GetAll",
+ &error,
+ G_TYPE_STRING,
+ "org.freedesktop.Accounts.User",
+ G_TYPE_INVALID,
+ dbus_g_type_get_map ("GHashTable", G_TYPE_STRING, G_TYPE_VALUE),
+ &hash_table,
+ G_TYPE_INVALID)) {
+ g_debug ("Error calling GetAll() when retrieving properties for %s: %s", object_path, error->message);
+ g_error_free (error);
+ g_free (props);
+ props = NULL;
+ goto out;
+ }
+ g_hash_table_foreach (hash_table, (GHFunc) collect_props, props);
+ g_hash_table_unref (hash_table);
+
+ out:
+ g_object_unref (proxy);
+ return props;
+}
+
+
+struct _UmUser {
+ GObject parent;
+
+ DBusGConnection *bus;
+ DBusGProxy *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);
+
+ dbus_g_connection_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);
+
+ return user->display_name ? user->display_name
+ : user->props->real_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;
+}
+
+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
+changed_handler (DBusGProxy *proxy,
+ gpointer *data)
+{
+ UmUser *user = UM_USER (data);
+
+ 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;
+
+ user = (UmUser *)g_object_new (UM_TYPE_USER, NULL);
+ user->object_path = g_strdup (object_path);
+
+ error = NULL;
+ user->bus = dbus_g_bus_get (DBUS_BUS_SYSTEM, &error);
+ if (user->bus == NULL) {
+ g_warning ("Couldn't connect to system bus: %s", error->message);
+ goto error;
+ }
+
+ user->proxy = dbus_g_proxy_new_for_name (user->bus,
+ "org.freedesktop.Accounts",
+ user->object_path,
+ "org.freedesktop.Accounts.User");
+ dbus_g_proxy_set_default_timeout (user->proxy, INT_MAX);
+ dbus_g_proxy_add_signal (user->proxy, "Changed", G_TYPE_INVALID);
+
+ dbus_g_proxy_connect_signal (user->proxy, "Changed",
+ G_CALLBACK (changed_handler), user, NULL);
+
+ 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)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetEmail",
+ &error,
+ G_TYPE_STRING, email,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetEmail call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_language (UmUser *user,
+ const gchar *language)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetLanguage",
+ &error,
+ G_TYPE_STRING, language,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetLanguage call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_location (UmUser *user,
+ const gchar *location)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetLocation",
+ &error,
+ G_TYPE_STRING, location,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetLocation call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_user_name (UmUser *user,
+ const gchar *user_name)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetUserName",
+ &error,
+ G_TYPE_STRING, user_name,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetUserName call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_real_name (UmUser *user,
+ const gchar *real_name)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetRealName",
+ &error,
+ G_TYPE_STRING, real_name,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetRealName call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+void
+um_user_set_icon_file (UmUser *user,
+ const gchar *icon_file)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetIconFile",
+ &error,
+ G_TYPE_STRING, icon_file,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetIconFile call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+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)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetAccountType",
+ &error,
+ G_TYPE_INT, account_type,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetAccountType call failed: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+}
+
+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) {
+ crypted = make_crypted (password);
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetPassword",
+ &error,
+ G_TYPE_STRING, crypted,
+ G_TYPE_STRING, hint,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetPassword call failed: %s", error->message);
+ g_error_free (error);
+ }
+ memset (crypted, 0, strlen (crypted));
+ g_free (crypted);
+ }
+ else if (password_mode == 3 || password_mode == 4) {
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetLocked",
+ &error,
+ G_TYPE_BOOLEAN, (password_mode == 3),
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetLocked call failed: %s", error->message);
+ g_error_free (error);
+ }
+ }
+ else {
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetPasswordMode",
+ &error,
+ G_TYPE_INT, password_mode,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetPasswordMode call failed: %s", error->message);
+ g_error_free (error);
+ }
+ }
+}
+
+gboolean
+um_user_is_logged_in (UmUser *user)
+{
+ DBusGProxy *proxy;
+ GPtrArray *array;
+ GError *error;
+ gint n_sessions;
+
+ proxy = dbus_g_proxy_new_for_name (user->bus,
+ "org.freedesktop.ConsoleKit",
+ "/org/freedesktop/ConsoleKit/Manager",
+ "org.freedesktop.ConsoleKit.Manager");
+
+ array = NULL;
+ error = NULL;
+ if (!dbus_g_proxy_call (proxy,
+ "GetSessionsForUnixUser",
+ &error,
+ G_TYPE_UINT, um_user_get_uid (user),
+ G_TYPE_INVALID,
+ dbus_g_type_get_collection ("GPtrArray", DBUS_TYPE_G_OBJECT_PATH), &array,
+ G_TYPE_INVALID)) {
+ g_warning ("GetSessionsForUnixUser failed: %s", error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ n_sessions = array->len;
+
+ g_ptr_array_foreach (array, (GFunc)g_free, NULL);
+ g_ptr_array_free (array, TRUE);
+
+ g_object_unref (proxy);
+
+ return n_sessions > 0;
+}
+
+
+void
+um_user_set_automatic_login (UmUser *user,
+ gboolean enabled)
+{
+ GError *error = NULL;
+
+ if (!dbus_g_proxy_call (user->proxy,
+ "SetAutomaticLogin",
+ &error,
+ G_TYPE_BOOLEAN, enabled,
+ G_TYPE_INVALID,
+ G_TYPE_INVALID)) {
+ g_warning ("SetAutomaticLogin call failed: %s", error->message);
+ g_error_free (error);
+ }
+}
+
+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;
+ }
+}
+
diff --git a/panels/user-accounts/um-user.h b/panels/user-accounts/um-user.h
new file mode 100644
index 000000000..43a88b011
--- /dev/null
+++ b/panels/user-accounts/um-user.h
@@ -0,0 +1,107 @@
+/* -*- 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
+ *
+ * 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
+ */
+
+/*
+ * Facade object for user data, owned by UmUserManager
+ */
+
+#ifndef __UM_USER__
+#define __UM_USER__
+
+#include
+#include
+#include
+
+#include "um-account-type.h"
+
+G_BEGIN_DECLS
+
+#define UM_TYPE_USER (um_user_get_type ())
+#define UM_USER(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), UM_TYPE_USER, UmUser))
+#define UM_IS_USER(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), UM_TYPE_USER))
+
+typedef enum {
+ UM_PASSWORD_MODE_REGULAR,
+ UM_PASSWORD_MODE_SET_AT_LOGIN,
+ UM_PASSWORD_MODE_NONE,
+ UM_PASSWORD_MODE_DISABLED,
+ UM_PASSWORD_MODE_ENABLED
+} UmPasswordMode;
+
+typedef struct _UmUser UmUser;
+
+GType um_user_get_type (void) G_GNUC_CONST;
+
+UmUser *um_user_new_from_object_path (const gchar *path);
+const gchar *um_user_get_object_path (UmUser *user);
+
+uid_t um_user_get_uid (UmUser *user);
+const gchar *um_user_get_user_name (UmUser *user);
+const gchar *um_user_get_real_name (UmUser *user);
+const gchar *um_user_get_display_name (UmUser *user);
+gint um_user_get_account_type (UmUser *user);
+const gchar *um_user_get_email (UmUser *user);
+const gchar *um_user_get_language (UmUser *user);
+const gchar *um_user_get_location (UmUser *user);
+const gchar *um_user_get_home_directory (UmUser *user);
+const gchar *um_user_get_shell (UmUser *user);
+gulong um_user_get_login_frequency (UmUser *user);
+gint um_user_get_password_mode (UmUser *user);
+const gchar *um_user_get_password_hint (UmUser *user);
+const gchar *um_user_get_icon_file (UmUser *user);
+gboolean um_user_get_locked (UmUser *user);
+gboolean um_user_get_automatic_login (UmUser *user);
+
+void um_user_set_user_name (UmUser *user,
+ const gchar *user_name);
+void um_user_set_real_name (UmUser *user,
+ const gchar *real_name);
+void um_user_set_email (UmUser *user,
+ const gchar *email);
+void um_user_set_language (UmUser *user,
+ const gchar *language);
+void um_user_set_location (UmUser *user,
+ const gchar *location);
+void um_user_set_icon_file (UmUser *user,
+ const gchar *filename);
+void um_user_set_icon_data (UmUser *user,
+ GdkPixbuf *pixbuf);
+void um_user_set_account_type (UmUser *user,
+ gint account_type);
+void um_user_set_automatic_login (UmUser *user,
+ gboolean enabled);
+void um_user_set_password (UmUser *user,
+ int password_mode,
+ const gchar *plain,
+ const gchar *password_hint);
+gboolean um_user_is_logged_in (UmUser *user);
+
+GdkPixbuf *um_user_render_icon (UmUser *user,
+ gboolean framed,
+ gint icon_size);
+gint um_user_collate (UmUser *user1,
+ UmUser *user2);
+
+void um_user_show_short_display_name (UmUser *user);
+void um_user_show_full_display_name (UmUser *user);
+
+G_END_DECLS
+
+#endif
diff --git a/panels/user-accounts/um-utils.c b/panels/user-accounts/um-utils.c
new file mode 100644
index 000000000..86a8add1b
--- /dev/null
+++ b/panels/user-accounts/um-utils.c
@@ -0,0 +1,382 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "um-utils.h"
+
+typedef struct {
+ gchar *text;
+ gchar *placeholder_str;
+ GIcon *icon;
+ gunichar placeholder;
+ gulong query_id;
+} IconShapeData;
+
+static IconShapeData *
+icon_shape_data_new (const gchar *text,
+ const gchar *placeholder,
+ GIcon *icon)
+{
+ IconShapeData *data;
+
+ data = g_new0 (IconShapeData, 1);
+
+ data->text = g_strdup (text);
+ data->placeholder_str = g_strdup (placeholder);
+ data->placeholder = g_utf8_get_char_validated (placeholder, -1);
+ data->icon = g_object_ref (icon);
+
+ return data;
+}
+
+static void
+icon_shape_data_free (gpointer user_data)
+{
+ IconShapeData *data = user_data;
+
+ g_free (data->text);
+ g_free (data->placeholder_str);
+ g_object_unref (data->icon);
+ g_free (data);
+}
+
+static void
+icon_shape_renderer (cairo_t *cr,
+ PangoAttrShape *attr,
+ gboolean do_path,
+ gpointer user_data)
+{
+ IconShapeData *data = user_data;
+ gdouble x, y;
+
+ cairo_get_current_point (cr, &x, &y);
+ if (GPOINTER_TO_UINT (attr->data) == data->placeholder) {
+ gdouble ascent;
+ gdouble height;
+ gdouble width;
+ GdkPixbuf *pixbuf;
+ GtkIconInfo *info;
+
+ ascent = pango_units_to_double (attr->ink_rect.y);
+ height = pango_units_to_double (attr->ink_rect.height);
+ width = pango_units_to_double (attr->ink_rect.width);
+ info = gtk_icon_theme_lookup_by_gicon (gtk_icon_theme_get_default (),
+ data->icon,
+ (gint)height,
+ GTK_ICON_LOOKUP_FORCE_SIZE | GTK_ICON_LOOKUP_USE_BUILTIN);
+ pixbuf = gtk_icon_info_load_icon (info, NULL);
+ gtk_icon_info_free (info);
+
+ cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
+ cairo_reset_clip (cr);
+ gdk_cairo_set_source_pixbuf (cr, pixbuf, x, y + ascent);
+ cairo_paint (cr);
+ g_object_unref (pixbuf);
+ }
+}
+
+static PangoAttrList *
+create_shape_attr_list_for_layout (PangoLayout *layout,
+ IconShapeData *data)
+{
+ PangoAttrList *attrs;
+ PangoFontMetrics *metrics;
+ gint ascent, descent;
+ PangoRectangle ink_rect, logical_rect;
+ const gchar *p;
+ const gchar *text;
+ gint placeholder_len;
+
+ /* Get font metrics and prepare fancy shape size */
+ metrics = pango_context_get_metrics (pango_layout_get_context (layout),
+ pango_layout_get_font_description (layout),
+ NULL);
+ ascent = pango_font_metrics_get_ascent (metrics);
+ descent = pango_font_metrics_get_descent (metrics);
+ pango_font_metrics_unref (metrics);
+
+ logical_rect.x = 0;
+ logical_rect.y = - ascent;
+ logical_rect.width = ascent + descent;
+ logical_rect.height = ascent + descent;
+
+ ink_rect = logical_rect;
+
+ attrs = pango_attr_list_new ();
+ text = pango_layout_get_text (layout);
+ placeholder_len = strlen (data->placeholder_str);
+ for (p = text; (p = strstr (p, data->placeholder_str)); p += placeholder_len) {
+ PangoAttribute *attr;
+
+ attr = pango_attr_shape_new_with_data (&ink_rect,
+ &logical_rect,
+ GUINT_TO_POINTER (g_utf8_get_char (p)),
+ NULL, NULL);
+
+ attr->start_index = p - text;
+ attr->end_index = attr->start_index + placeholder_len;
+
+ pango_attr_list_insert (attrs, attr);
+ }
+
+ return attrs;
+}
+
+static gboolean
+query_unlock_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_tooltip,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ GtkWidget *label;
+ PangoLayout *layout;
+ PangoAttrList *attrs;
+ IconShapeData *data;
+
+ data = g_object_get_data (G_OBJECT (widget), "icon-shape-data");
+ label = g_object_get_data (G_OBJECT (widget), "tooltip-label");
+ if (label == NULL) {
+ label = gtk_label_new (data->text);
+ g_object_ref_sink (label);
+ g_object_set_data_full (G_OBJECT (widget),
+ "tooltip-label", label, g_object_unref);
+ }
+
+ layout = gtk_label_get_layout (GTK_LABEL (label));
+ pango_cairo_context_set_shape_renderer (pango_layout_get_context (layout),
+ icon_shape_renderer,
+ data, NULL);
+
+ attrs = create_shape_attr_list_for_layout (layout, data);
+ gtk_label_set_attributes (GTK_LABEL (label), attrs);
+ pango_attr_list_unref (attrs);
+
+ gtk_tooltip_set_custom (tooltip, label);
+
+ return TRUE;
+}
+
+void
+setup_tooltip_with_embedded_icon (GtkWidget *widget,
+ const gchar *text,
+ const gchar *placeholder,
+ GIcon *icon)
+{
+ IconShapeData *data;
+
+ data = g_object_get_data (G_OBJECT (widget), "icon-shape-data");
+ if (data) {
+ gtk_widget_set_has_tooltip (widget, FALSE);
+ g_signal_handler_disconnect (widget, data->query_id);
+ g_object_set_data (G_OBJECT (widget), "icon-shape-data", NULL);
+ g_object_set_data (G_OBJECT (widget), "tooltip-label", NULL);
+ }
+
+ if (!placeholder) {
+ gtk_widget_set_tooltip_text (widget, text);
+ return;
+ }
+
+ data = icon_shape_data_new (text, placeholder, icon);
+ g_object_set_data_full (G_OBJECT (widget),
+ "icon-shape-data",
+ data,
+ icon_shape_data_free);
+
+ gtk_widget_set_has_tooltip (widget, TRUE);
+ data->query_id = g_signal_connect (widget, "query-tooltip",
+ G_CALLBACK (query_unlock_tooltip), NULL);
+
+}
+
+gboolean
+show_tooltip_now (GtkWidget *widget,
+ GdkEvent *event)
+{
+ GtkSettings *settings;
+ gint timeout;
+
+ settings = gtk_widget_get_settings (widget);
+
+ g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL);
+ g_object_set (settings, "gtk-tooltip-timeout", 1, NULL);
+ gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (widget));
+ g_object_set (settings, "gtk-tooltip-timeout", timeout, NULL);
+
+ return FALSE;
+}
+
+static gboolean
+query_tooltip (GtkWidget *widget,
+ gint x,
+ gint y,
+ gboolean keyboard_mode,
+ GtkTooltip *tooltip,
+ gpointer user_data)
+{
+ gchar *tip;
+
+ if (GTK_ENTRY_ICON_SECONDARY == gtk_entry_get_icon_at_pos (GTK_ENTRY (widget), x, y)) {
+ tip = gtk_entry_get_icon_tooltip_text (GTK_ENTRY (widget),
+ GTK_ENTRY_ICON_SECONDARY);
+ gtk_tooltip_set_text (tooltip, tip);
+ g_free (tip);
+
+ return TRUE;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+static void
+icon_released (GtkEntry *entry,
+ GtkEntryIconPosition pos,
+ GdkEvent *event,
+ gpointer user_data)
+{
+ GtkSettings *settings;
+ gint timeout;
+
+ settings = gtk_widget_get_settings (GTK_WIDGET (entry));
+
+ g_object_get (settings, "gtk-tooltip-timeout", &timeout, NULL);
+ g_object_set (settings, "gtk-tooltip-timeout", 1, NULL);
+ gtk_tooltip_trigger_tooltip_query (gtk_widget_get_display (GTK_WIDGET (entry)));
+ g_object_set (settings, "gtk-tooltip-timeout", timeout, NULL);
+}
+
+
+void
+set_entry_validation_error (GtkEntry *entry,
+ const gchar *text)
+{
+ g_object_set (entry, "caps-lock-warning", FALSE, NULL);
+ gtk_entry_set_icon_from_stock (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ GTK_STOCK_DIALOG_ERROR);
+ gtk_entry_set_icon_activatable (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ TRUE);
+ g_signal_connect (entry, "icon-release",
+ G_CALLBACK (icon_released), FALSE);
+ g_signal_connect (entry, "query-tooltip",
+ G_CALLBACK (query_tooltip), NULL);
+ g_object_set (entry, "has-tooltip", TRUE, NULL);
+ gtk_entry_set_icon_tooltip_text (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ text);
+}
+
+void
+clear_entry_validation_error (GtkEntry *entry)
+{
+ gboolean warning;
+
+ g_object_get (entry, "caps-lock-warning", &warning, NULL);
+
+ if (warning)
+ return;
+
+ g_object_set (entry, "has-tooltip", FALSE, NULL);
+ gtk_entry_set_icon_from_pixbuf (entry,
+ GTK_ENTRY_ICON_SECONDARY,
+ NULL);
+ g_object_set (entry, "caps-lock-warning", TRUE, NULL);
+}
+
+void
+popup_menu_below_button (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ GtkWidget *button)
+{
+ GtkRequisition menu_req;
+ GtkTextDirection direction;
+ GtkAllocation allocation;
+
+ gtk_widget_size_request (GTK_WIDGET (menu), &menu_req);
+
+ direction = gtk_widget_get_direction (button);
+
+ gdk_window_get_origin (gtk_widget_get_window (button), x, y);
+ gtk_widget_get_allocation (button, &allocation);
+ *x += allocation.x;
+ *y += allocation.y + allocation.height;
+
+ if (direction == GTK_TEXT_DIR_LTR)
+ *x += MAX (allocation.width - menu_req.width, 0);
+ else if (menu_req.width > allocation.width)
+ *x -= menu_req.width - allocation.width;
+
+ *push_in = TRUE;
+}
+
+void
+rounded_rectangle (cairo_t *cr,
+ gdouble aspect,
+ gdouble x,
+ gdouble y,
+ gdouble corner_radius,
+ gdouble width,
+ gdouble height)
+{
+ gdouble radius;
+ gdouble degrees;
+
+ radius = corner_radius / aspect;
+ degrees = G_PI / 180.0;
+
+ cairo_new_sub_path (cr);
+ cairo_arc (cr,
+ x + width - radius,
+ y + radius,
+ radius,
+ -90 * degrees,
+ 0 * degrees);
+ cairo_arc (cr,
+ x + width - radius,
+ y + height - radius,
+ radius,
+ 0 * degrees,
+ 90 * degrees);
+ cairo_arc (cr,
+ x + radius,
+ y + height - radius,
+ radius,
+ 90 * degrees,
+ 180 * degrees);
+ cairo_arc (cr,
+ x + radius,
+ y + radius,
+ radius,
+ 180 * degrees,
+ 270 * degrees);
+ cairo_close_path (cr);
+}
+
diff --git a/panels/user-accounts/um-utils.h b/panels/user-accounts/um-utils.h
new file mode 100644
index 000000000..d6a227e78
--- /dev/null
+++ b/panels/user-accounts/um-utils.h
@@ -0,0 +1,56 @@
+/* -*- 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 3 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.
+ *
+ * Written by: Matthias Clasen
+ */
+
+#ifndef __UM_UTILS_H__
+#define __UM_UTILS_H__
+
+#include
+
+G_BEGIN_DECLS
+
+void setup_tooltip_with_embedded_icon (GtkWidget *widget,
+ const gchar *text,
+ const gchar *placeholder,
+ GIcon *icon);
+gboolean show_tooltip_now (GtkWidget *widget,
+ GdkEvent *event);
+
+void set_entry_validation_error (GtkEntry *entry,
+ const gchar *text);
+void clear_entry_validation_error (GtkEntry *entry);
+
+void popup_menu_below_button (GtkMenu *menu,
+ gint *x,
+ gint *y,
+ gboolean *push_in,
+ GtkWidget *button);
+
+void rounded_rectangle (cairo_t *cr,
+ gdouble aspect,
+ gdouble x,
+ gdouble y,
+ gdouble corner_radius,
+ gdouble width,
+ gdouble height);
+
+G_END_DECLS
+
+#endif
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e3cdea42a..186dbf2a2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -64,6 +64,25 @@ panels/sound/data/gnome-sound-panel.desktop.in.in
panels/sound/data/sounds/gnome-sounds-default.xml.in.in
panels/universal-access/gnome-universal-access-panel.desktop.in.in
[type: gettext/glade]panels/universal-access/uap.ui
+panels/user-accounts/gdm-languages.c
+panels/user-accounts/run-passwd.c
+panels/user-accounts/um-account-dialog.c
+panels/user-accounts/um-account-type.c
+panels/user-accounts/um-fingerprint-dialog.c
+panels/user-accounts/um-language-dialog.c
+panels/user-accounts/um-lockbutton.c
+panels/user-accounts/um-login-options.c
+panels/user-accounts/um-password-dialog.c
+panels/user-accounts/um-photo-dialog.c
+panels/user-accounts/um-user-manager.c
+panels/user-accounts/um-user-panel.c
+panels/user-accounts/data/gnome-user-accounts-panel.desktop.in.in
+[type: gettext/glade]panels/user-accounts/data/account-dialog.ui
+[type: gettext/glade]panels/user-accounts/data/language-chooser.ui
+[type: gettext/glade]panels/user-accounts/data/password-dialog.ui
+[type: gettext/glade]panels/user-accounts/data/photo-dialog.ui
+[type: gettext/glade]panels/user-accounts/data/user-accounts-dialog.ui
+[type: gettext/glade]panels/user-accounts/data/account-fingerprint.ui
shell/control-center.c
shell/gnome-control-center.desktop.in.in
shell/gnomecc.directory.in
diff --git a/po/POTFILES.skip b/po/POTFILES.skip
index cb6a3a1d8..93aeda3ea 100644
--- a/po/POTFILES.skip
+++ b/po/POTFILES.skip
@@ -10,6 +10,8 @@ panels/keybindings/gnome-keybindings-panel.desktop.in
panels/keyboard/gnome-keyboard-panel.desktop.in
panels/sound/data/gnome-sound-panel.desktop.in
panels/sound/data/sounds/gnome-sounds-default.xml.in
+panels/user-accounts/data/gnome-user-accounts-panel.desktop.in
+panels/user-accounts/fingerprint-strings.h
capplets/localization/localization.desktop.in
capplets/mouse/gnome-settings-mouse.desktop.in
panels/network/gnome-network-panel.desktop.in