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 @@ + + + + + + + + + + + + + + + + + + + + Standard + 0 + + + Administrator + 1 + + + Supervised + 2 + + + + + 5 + + False + True + center-on-parent + system-users + dialog + + + True + vertical + 2 + + + True + 10 + 6 + + + True + 5 + 2 + 6 + 6 + + + True + username-model + 0 + + + True + + + + + 1 + 2 + 4 + 5 + + + + + True + 1 + _Username: + True + username-combo + + + + + + 4 + 5 + GTK_FILL + + + + + True + + + True + + + 0 + + + + + GTK_FILL + + + + + True + Create new account + + + + + + + 2 + + + + + True + + + 1 + 2 + GTK_FILL + + + + + True + + + 1 + 2 + 1 + 2 + + + + + True + True + True + + + 1 + 2 + 3 + 4 + + + + + True + 1 + _Full name: + True + name-entry + + + + + + 3 + 4 + GTK_FILL + + + + + True + 1 + _Account Type: + True + account-type-combo + + + + + + 2 + 3 + + + + + True + account-type-model + + + + 0 + + + + + 1 + 2 + 2 + 3 + + + + + False + False + 0 + + + + + False + False + 1 + + + + + True + end + + + gtk-cancel + True + True + False + True + + + False + False + 0 + + + + + Cr_eate + True + True + True + True + True + + + False + False + 1 + + + + + False + end + 0 + + + + + + cancel-button + ok-button + + + 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + + \ 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