Use AccountsService for setting name and email (improves LP: #1402036), improve name detection on startup (fixes LP: #1304920, #1394064, #1427651, #1549717, #1559813, #1559815)

This commit is contained in:
Sean Davis 2016-03-27 22:02:23 -04:00
parent 2255414e3e
commit c90e392207
3 changed files with 301 additions and 116 deletions

View File

@ -32,7 +32,7 @@ from gi.repository import Gtk, GdkPixbuf, GLib, Gio # pylint: disable=E0611
import logging import logging
logger = logging.getLogger('mugshot') logger = logging.getLogger('mugshot')
from mugshot_lib import Window, SudoDialog, helpers from mugshot_lib import Window, SudoDialog, AccountsServiceAdapter, helpers
try: try:
from mugshot.CameraMugshotDialog import CameraMugshotDialog from mugshot.CameraMugshotDialog import CameraMugshotDialog
@ -219,6 +219,9 @@ class MugshotWindow(Window):
self.tmpfile = None self.tmpfile = None
self.accounts_service = \
AccountsServiceAdapter.MugshotAccountsServiceAdapter(username)
# Populate all of the widgets. # Populate all of the widgets.
self.init_user_details() self.init_user_details()
@ -233,17 +236,17 @@ class MugshotWindow(Window):
# Check for .face and set profile image. # Check for .face and set profile image.
logger.debug('Checking for ~/.face profile image') logger.debug('Checking for ~/.face profile image')
face = os.path.join(home, '.face') face = os.path.join(home, '.face')
logger.debug('Checking AccountsService for profile image')
image = self.accounts_service_get_user_image()
# AccountsService may not be supported or desired. logger.debug('Checking AccountsService for profile image')
if image is None: if not self.accounts_service.available():
# AccountsService may not be supported or desired.
logger.debug("AccountsService is not supported.") logger.debug("AccountsService is not supported.")
self.updated_image = face self.updated_image = face
self.set_user_image(face) self.set_user_image(face)
# If it is supported, process and compare to ~/.face # If it is supported, process and compare to ~/.face
else: else:
image = self.accounts_service.get_icon_file()
logger.debug('Found profile image: %s' % str(image)) logger.debug('Found profile image: %s' % str(image))
if os.path.isfile(face): if os.path.isfile(face):
@ -284,7 +287,7 @@ class MugshotWindow(Window):
def set_user_image(self, filename=None): def set_user_image(self, filename=None):
"""Scale and set the user profile image.""" """Scale and set the user profile image."""
logger.debug("Setting user profile image to %s" % str(filename)) logger.debug("Setting user profile image to %s" % str(filename))
if filename: if filename and os.path.exists(filename):
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
scaled = pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER) scaled = pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER)
self.user_image.set_from_pixbuf(scaled) self.user_image.set_from_pixbuf(scaled)
@ -343,6 +346,9 @@ class MugshotWindow(Window):
dialog.destroy() dialog.destroy()
return return
if self.get_as_details_updated():
self.save_as_details()
if self.get_libreoffice_details_updated(): if self.get_libreoffice_details_updated():
self.set_libreoffice_data() self.set_libreoffice_data()
@ -412,8 +418,9 @@ class MugshotWindow(Window):
shutil.copyfile(self.updated_image, face) shutil.copyfile(self.updated_image, face)
# Update AccountsService profile image # Update AccountsService profile image
logger.debug('Photo updated, saving AccountsService profile image.') if self.accounts_service.available():
self.accounts_service_set_user_image(self.updated_image) logger.debug('Photo updated, saving AccountsService profile image.')
self.accounts_service.set_icon_file(self.updated_image)
# Update Pidgin buddy icon # Update Pidgin buddy icon
self.set_pidgin_buddyicon(self.updated_image) self.set_pidgin_buddyicon(self.updated_image)
@ -421,64 +428,6 @@ class MugshotWindow(Window):
self.updated_image = None self.updated_image = None
return True return True
def accounts_service_get_user_image(self):
"""Get user profile image using AccountsService."""
try:
bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
result = bus.call_sync('org.freedesktop.Accounts',
'/org/freedesktop/Accounts',
'org.freedesktop.Accounts',
'FindUserByName',
GLib.Variant('(s)', (username,)),
GLib.VariantType.new('(o)'),
Gio.DBusCallFlags.NONE,
-1,
None)
(path,) = result.unpack()
variant = GLib.Variant('(s)',
('org.freedesktop.Accounts.User',))
result = bus.call_sync('org.freedesktop.Accounts',
path,
'org.freedesktop.DBus.Properties',
'GetAll',
variant,
GLib.VariantType.new('(a{sv})'),
Gio.DBusCallFlags.NONE,
-1,
None)
(props,) = result.unpack()
return props['IconFile']
except GLib.GError:
return None
def accounts_service_set_user_image(self, filename):
"""Set user profile image using AccountsService."""
try:
bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
result = bus.call_sync('org.freedesktop.Accounts',
'/org/freedesktop/Accounts',
'org.freedesktop.Accounts',
'FindUserByName',
GLib.Variant('(s)', (username,)),
GLib.VariantType.new('(o)'),
Gio.DBusCallFlags.NONE,
-1,
None)
(path,) = result.unpack()
bus.call_sync('org.freedesktop.Accounts',
path,
'org.freedesktop.Accounts.User',
'SetIconFile',
GLib.Variant('(s)', (filename,)),
GLib.VariantType.new('()'),
Gio.DBusCallFlags.NONE,
-1,
None)
except GLib.GError:
pass
def set_pidgin_buddyicon(self, filename=None): def set_pidgin_buddyicon(self, filename=None):
"""Sets the pidgin buddyicon to filename (usually ~/.face). """Sets the pidgin buddyicon to filename (usually ~/.face).
@ -535,14 +484,37 @@ class MugshotWindow(Window):
write_prefs.close() write_prefs.close()
# = chfn functions ============================================ # # = chfn functions ============================================ #
def get_as_details_updated(self):
if not self.accounts_service.available():
return False
if self.first_name != self.first_name_entry.get_text().strip():
return True
if self.last_name != self.last_name_entry.get_text().strip():
return True
if self.email != self.email_entry.get_text().strip():
return True
def save_as_details(self):
if not self.accounts_service.available():
return True
first_name = get_entry_value(self.first_name_entry)
last_name = get_entry_value(self.last_name_entry)
full_name = "%s %s" % (first_name, last_name)
full_name = full_name.strip()
email = get_entry_value(self.email_entry)
self.accounts_service.set_real_name(full_name)
self.accounts_service.set_email(email)
return True
def get_chfn_details_updated(self): def get_chfn_details_updated(self):
"""Return True if chfn-related details have been modified.""" """Return True if chfn-related details have been modified."""
logger.debug('Checking if chfn details have been modified.') logger.debug('Checking if chfn details have been modified.')
modified = False modified = False
if self.first_name != self.first_name_entry.get_text().strip(): if not self.accounts_service.available():
modified = True if self.first_name != self.first_name_entry.get_text().strip():
if self.last_name != self.last_name_entry.get_text().strip(): modified = True
modified = True if self.last_name != self.last_name_entry.get_text().strip():
modified = True
if self.home_phone != self.home_phone_entry.get_text().strip(): if self.home_phone != self.home_phone_entry.get_text().strip():
modified = True modified = True
if self.office_phone != self.office_phone_entry.get_text().strip(): if self.office_phone != self.office_phone_entry.get_text().strip():
@ -608,14 +580,16 @@ class MugshotWindow(Window):
home_phone = 'none' home_phone = 'none'
# Full name can only be modified by root. Try using sudo to modify. # Full name can only be modified by root. Try using sudo to modify.
if SudoDialog.check_dependencies(['chfn']): if not self.accounts_service.available():
logger.debug('Updating Full Name...') if SudoDialog.check_dependencies(['chfn']):
command = "%s %s -f \"%s\" %s" % (sudo, chfn, full_name, username) logger.debug('Updating Full Name...')
if self.process_terminal_password(command, password): command = "%s %s -f \"%s\" %s" % (sudo, chfn, full_name,
self.first_name = first_name username)
self.last_name = last_name if self.process_terminal_password(command, password):
else: self.first_name = first_name
success = False self.last_name = last_name
else:
success = False
logger.debug('Updating Home Phone...') logger.debug('Updating Home Phone...')
command = "%s -h \"%s\" %s" % (chfn, home_phone, username) command = "%s -h \"%s\" %s" % (chfn, home_phone, username)
@ -666,29 +640,73 @@ class MugshotWindow(Window):
# Start with LibreOffice, as users may have configured that first. # Start with LibreOffice, as users may have configured that first.
data = self.get_libreoffice_data() data = self.get_libreoffice_data()
# Next is passwd, where we override name values with system values. # Prefer AccountsService, GLib, then passwd
as_data = self.get_accounts_service_data()
gl_data = self.get_glib_data()
pwd_data = self.get_passwd_data() pwd_data = self.get_passwd_data()
data['first_name'] = pwd_data['first_name']
data['last_name'] = pwd_data['last_name'] for dataset in [as_data, gl_data, pwd_data]:
data['initials'] = pwd_data['initials'] if dataset is None:
if len(data['home_phone']) == 0: continue
data['home_phone'] = pwd_data['home_phone'] if len(dataset['first_name']) > 0:
if len(data['office_phone']) == 0: data['first_name'] = dataset['first_name']
data['office_phone'] = pwd_data['office_phone'] data['last_name'] = dataset['last_name']
data['initials'] = dataset['initials']
break
for dataset in [as_data, gl_data, pwd_data]:
if dataset is None:
continue
for key in ['home_phone', 'office_phone', 'email', 'fax']:
if len(data[key]) == 0:
data[key] = dataset[key]
# Then get data from dconf # Then get data from dconf
initials = self.settings['initials'] if len(self.settings['initials']) > len(data['initials']):
if len(initials) > 0: data['initials'] = self.settings['initials']
data['initials'] = initials
email = self.settings['email']
if len(data['email']) == 0: if len(data['email']) == 0:
data['email'] = email data['email'] = self.settings['email']
if len(data['fax']) == 0: if len(data['fax']) == 0:
data['fax'] = self.settings['fax'] data['fax'] = self.settings['fax']
# Return all of the finalized information. # Return all of the finalized information.
return data return data
def split_name(self, name):
parts = name.split()
initials = ""
first = ""
last = ""
if len(' '.join(parts)) > 0:
first = parts[0]
if len(parts) > 1:
last = ' '.join(parts[1:])
for part in parts:
if len(part) > 0:
initials += part[0]
return {
"first": first, "last": last, "initials": initials
}
def get_accounts_service_data(self):
if not self.accounts_service.available():
return None
name = self.accounts_service.get_real_name()
name = self.split_name(name)
email = self.accounts_service.get_email()
data = {'first_name': name['first'], 'last_name': name['last'],
'home_phone': '', 'office_phone': '',
'initials': name['initials'], 'email': email, 'fax': ''}
return data
def get_glib_data(self):
name = GLib.get_real_name()
name = self.split_name(name)
data = {'first_name': name['first'], 'last_name': name['last'],
'home_phone': '', 'office_phone': '',
'initials': name['initials'], 'email': '', 'fax': ''}
def get_passwd_data(self): def get_passwd_data(self):
"""Get user details from passwd""" """Get user details from passwd"""
# Use getent for current user's details. # Use getent for current user's details.
@ -711,18 +729,9 @@ class MugshotWindow(Window):
office = "" office = ""
office_phone = "" office_phone = ""
home_phone = "" home_phone = ""
name = ""
# Use GLib to get the actual name. name = self.split_name(name)
name = GLib.get_real_name()
# Expand the user's fullname into first, last, and initials.
initials = name[0]
try:
first_name, last_name = name.split(' ', 1)
initials += last_name[0]
except:
first_name = name
last_name = ''
# If the variables are defined as 'none', use blank for cleanliness. # If the variables are defined as 'none', use blank for cleanliness.
if home_phone == 'none': if home_phone == 'none':
@ -731,9 +740,9 @@ class MugshotWindow(Window):
office_phone = '' office_phone = ''
# Pack the data # Pack the data
data = {'first_name': first_name, 'last_name': last_name, data = {'first_name': name['first'], 'last_name': name['last'],
'home_phone': home_phone, 'office_phone': office_phone, 'home_phone': home_phone, 'office_phone': office_phone,
'initials': initials, 'email': '', 'fax': ''} 'initials': name['initials'], 'email': '', 'fax': ''}
return data return data

View File

@ -0,0 +1,176 @@
#!/usr/bin/python3
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
# Mugshot - Lightweight user configuration utility
# Copyright (C) 2013-2015 Sean Davis <smd.seandavis@gmail.com>
#
# 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 warranties of
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
from gi.repository import Gio, GLib
class MugshotAccountsServiceAdapter:
_properties = {
"AutomaticLogin": bool,
"Locked": bool,
"AccountType": int,
"PasswordMode": int,
"SystemAccount": bool,
"Email": str,
"HomeDirectory": str,
"IconFile": str,
"Language": str,
"Location": str,
"RealName": str,
"Shell": str,
"UserName": str,
"XSession": str,
"Uid": int,
"LoginFrequency": int,
"PasswordHint": str,
"Domain": str,
"CredentialLifetime": int
}
def __init__(self, username):
self._set_username(username)
self._available = False
try:
self._get_path()
self._available = True
except:
pass
def available(self):
return self._available
def _set_username(self, username):
self._username = username
def _get_username(self):
return self._username
def _get_path(self):
return self._find_user_by_name(self._username)
def _get_variant(self, vtype, value):
if vtype == bool:
variant = "(b)"
elif vtype == int:
variant = "(i)"
elif vtype == str:
variant = "(s)"
variant = GLib.Variant(variant, (value,))
return variant
def _set_property(self, key, value):
if key not in list(self._properties.keys()):
return False
method = "Set" + key
variant = self._get_variant(self._properties[key], value)
try:
bus = self._get_bus()
path = self._find_user_by_name(self._username)
bus.call_sync('org.freedesktop.Accounts',
self._get_path(),
'org.freedesktop.Accounts.User',
method, variant,
GLib.VariantType.new('()'),
Gio.DBusCallFlags.NONE,
-1, None)
return True
except:
return False
def _get_all(self, ):
try:
bus = self._get_bus()
variant = GLib.Variant('(s)',
('org.freedesktop.Accounts.User',))
result = bus.call_sync('org.freedesktop.Accounts',
self._get_path(),
'org.freedesktop.DBus.Properties',
'GetAll',
variant,
GLib.VariantType.new('(a{sv})'),
Gio.DBusCallFlags.NONE,
-1,
None)
(props,) = result.unpack()
return props
except:
return None
def _get_property(self, key):
if key not in list(self._properties.keys()):
return False
props = self._get_all()
if props is not None:
return props[key]
return False
def _get_bus(self):
try:
bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
return bus
except:
return None
def _find_user_by_name(self, username):
try:
bus = self._get_bus()
result = bus.call_sync('org.freedesktop.Accounts',
'/org/freedesktop/Accounts',
'org.freedesktop.Accounts',
'FindUserByName',
GLib.Variant('(s)', (username,)),
GLib.VariantType.new('(o)'),
Gio.DBusCallFlags.NONE,
-1,
None)
(path,) = result.unpack()
return path
except:
return None
def get_email(self):
return self._get_property("Email")
def set_email(self, email):
self._set_property("Email", email)
def get_location(self):
return self._get_property("Location")
def set_location(self, location):
self._set_property("Location", location)
def get_icon_file(self):
"""Get user profile image using AccountsService."""
return self._get_property("IconFile")
def set_icon_file(self, filename):
"""Set user profile image using AccountsService."""
self._set_property("IconFile", filename)
def get_real_name(self):
return self._get_property("RealName")
def set_real_name(self, name):
"""Set user profile image using AccountsService."""
self._set_property("RealName", name)

View File

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-03-26 22:11-0400\n" "POT-Creation-Date: 2016-03-27 21:51-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -61,7 +61,7 @@ msgstr ""
msgid "Browse…" msgid "Browse…"
msgstr "" msgstr ""
#: ../data/ui/MugshotWindow.ui.h:9 ../mugshot/MugshotWindow.py:581 #: ../data/ui/MugshotWindow.ui.h:9 ../mugshot/MugshotWindow.py:553
msgid "Mugshot" msgid "Mugshot"
msgstr "" msgstr ""
@ -107,45 +107,45 @@ msgstr ""
msgid "Retry" msgid "Retry"
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:328 #: ../mugshot/MugshotWindow.py:331
msgid "Authentication cancelled." msgid "Authentication cancelled."
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:331 #: ../mugshot/MugshotWindow.py:334
msgid "Authentication failed." msgid "Authentication failed."
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:334 #: ../mugshot/MugshotWindow.py:337
msgid "An error occurred when saving changes." msgid "An error occurred when saving changes."
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:336 #: ../mugshot/MugshotWindow.py:339
msgid "User details were not updated." msgid "User details were not updated."
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:491 #: ../mugshot/MugshotWindow.py:440
msgid "Update Pidgin buddy icon?" msgid "Update Pidgin buddy icon?"
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:492 #: ../mugshot/MugshotWindow.py:441
msgid "Would you also like to update your Pidgin buddy icon?" msgid "Would you also like to update your Pidgin buddy icon?"
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:582 #: ../mugshot/MugshotWindow.py:554
msgid "Enter your password to change user details." msgid "Enter your password to change user details."
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:584 #: ../mugshot/MugshotWindow.py:556
msgid "" msgid ""
"This is a security measure to prevent unwanted updates\n" "This is a security measure to prevent unwanted updates\n"
"to your personal information." "to your personal information."
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:789 #: ../mugshot/MugshotWindow.py:798
msgid "Update LibreOffice user details?" msgid "Update LibreOffice user details?"
msgstr "" msgstr ""
#: ../mugshot/MugshotWindow.py:790 #: ../mugshot/MugshotWindow.py:799
msgid "Would you also like to update your user details in LibreOffice?" msgid "Would you also like to update your user details in LibreOffice?"
msgstr "" msgstr ""