diff --git a/mugshot/MugshotWindow.py b/mugshot/MugshotWindow.py index 96e391d..bcae91c 100644 --- a/mugshot/MugshotWindow.py +++ b/mugshot/MugshotWindow.py @@ -32,7 +32,7 @@ from gi.repository import Gtk, GdkPixbuf, GLib, Gio # pylint: disable=E0611 import logging logger = logging.getLogger('mugshot') -from mugshot_lib import Window, SudoDialog, helpers +from mugshot_lib import Window, SudoDialog, AccountsServiceAdapter, helpers try: from mugshot.CameraMugshotDialog import CameraMugshotDialog @@ -219,6 +219,9 @@ class MugshotWindow(Window): self.tmpfile = None + self.accounts_service = \ + AccountsServiceAdapter.MugshotAccountsServiceAdapter(username) + # Populate all of the widgets. self.init_user_details() @@ -233,17 +236,17 @@ class MugshotWindow(Window): # Check for .face and set profile image. logger.debug('Checking for ~/.face profile image') 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. - if image is None: + logger.debug('Checking AccountsService for profile image') + if not self.accounts_service.available(): + # AccountsService may not be supported or desired. logger.debug("AccountsService is not supported.") self.updated_image = face self.set_user_image(face) # If it is supported, process and compare to ~/.face else: + image = self.accounts_service.get_icon_file() logger.debug('Found profile image: %s' % str(image)) if os.path.isfile(face): @@ -284,7 +287,7 @@ class MugshotWindow(Window): def set_user_image(self, filename=None): """Scale and set the user profile image.""" 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) scaled = pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER) self.user_image.set_from_pixbuf(scaled) @@ -343,6 +346,9 @@ class MugshotWindow(Window): dialog.destroy() return + if self.get_as_details_updated(): + self.save_as_details() + if self.get_libreoffice_details_updated(): self.set_libreoffice_data() @@ -412,8 +418,9 @@ class MugshotWindow(Window): shutil.copyfile(self.updated_image, face) # Update AccountsService profile image - logger.debug('Photo updated, saving AccountsService profile image.') - self.accounts_service_set_user_image(self.updated_image) + if self.accounts_service.available(): + logger.debug('Photo updated, saving AccountsService profile image.') + self.accounts_service.set_icon_file(self.updated_image) # Update Pidgin buddy icon self.set_pidgin_buddyicon(self.updated_image) @@ -421,64 +428,6 @@ class MugshotWindow(Window): self.updated_image = None 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): """Sets the pidgin buddyicon to filename (usually ~/.face). @@ -535,14 +484,37 @@ class MugshotWindow(Window): write_prefs.close() # = 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): """Return True if chfn-related details have been modified.""" logger.debug('Checking if chfn details have been modified.') modified = False - if self.first_name != self.first_name_entry.get_text().strip(): - modified = True - if self.last_name != self.last_name_entry.get_text().strip(): - modified = True + if not self.accounts_service.available(): + if self.first_name != self.first_name_entry.get_text().strip(): + 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(): modified = True if self.office_phone != self.office_phone_entry.get_text().strip(): @@ -608,14 +580,16 @@ class MugshotWindow(Window): home_phone = 'none' # Full name can only be modified by root. Try using sudo to modify. - if SudoDialog.check_dependencies(['chfn']): - logger.debug('Updating Full Name...') - command = "%s %s -f \"%s\" %s" % (sudo, chfn, full_name, username) - if self.process_terminal_password(command, password): - self.first_name = first_name - self.last_name = last_name - else: - success = False + if not self.accounts_service.available(): + if SudoDialog.check_dependencies(['chfn']): + logger.debug('Updating Full Name...') + command = "%s %s -f \"%s\" %s" % (sudo, chfn, full_name, + username) + if self.process_terminal_password(command, password): + self.first_name = first_name + self.last_name = last_name + else: + success = False logger.debug('Updating Home Phone...') 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. 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() - data['first_name'] = pwd_data['first_name'] - data['last_name'] = pwd_data['last_name'] - data['initials'] = pwd_data['initials'] - if len(data['home_phone']) == 0: - data['home_phone'] = pwd_data['home_phone'] - if len(data['office_phone']) == 0: - data['office_phone'] = pwd_data['office_phone'] + + for dataset in [as_data, gl_data, pwd_data]: + if dataset is None: + continue + if len(dataset['first_name']) > 0: + data['first_name'] = dataset['first_name'] + 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 - initials = self.settings['initials'] - if len(initials) > 0: - data['initials'] = initials - email = self.settings['email'] + if len(self.settings['initials']) > len(data['initials']): + data['initials'] = self.settings['initials'] if len(data['email']) == 0: - data['email'] = email + data['email'] = self.settings['email'] if len(data['fax']) == 0: data['fax'] = self.settings['fax'] # Return all of the finalized information. 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): """Get user details from passwd""" # Use getent for current user's details. @@ -711,18 +729,9 @@ class MugshotWindow(Window): office = "" office_phone = "" home_phone = "" + name = "" - # Use GLib to get the actual 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 = '' + name = self.split_name(name) # If the variables are defined as 'none', use blank for cleanliness. if home_phone == 'none': @@ -731,9 +740,9 @@ class MugshotWindow(Window): office_phone = '' # 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, - 'initials': initials, 'email': '', 'fax': ''} + 'initials': name['initials'], 'email': '', 'fax': ''} return data diff --git a/mugshot_lib/AccountsServiceAdapter.py b/mugshot_lib/AccountsServiceAdapter.py new file mode 100644 index 0000000..d699a49 --- /dev/null +++ b/mugshot_lib/AccountsServiceAdapter.py @@ -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 +# +# 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 . + +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) diff --git a/po/mugshot.pot b/po/mugshot.pot index a52c552..d3042e6 100644 --- a/po/mugshot.pot +++ b/po/mugshot.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\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" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -61,7 +61,7 @@ msgstr "" msgid "Browse…" msgstr "" -#: ../data/ui/MugshotWindow.ui.h:9 ../mugshot/MugshotWindow.py:581 +#: ../data/ui/MugshotWindow.ui.h:9 ../mugshot/MugshotWindow.py:553 msgid "Mugshot" msgstr "" @@ -107,45 +107,45 @@ msgstr "" msgid "Retry" msgstr "" -#: ../mugshot/MugshotWindow.py:328 +#: ../mugshot/MugshotWindow.py:331 msgid "Authentication cancelled." msgstr "" -#: ../mugshot/MugshotWindow.py:331 +#: ../mugshot/MugshotWindow.py:334 msgid "Authentication failed." msgstr "" -#: ../mugshot/MugshotWindow.py:334 +#: ../mugshot/MugshotWindow.py:337 msgid "An error occurred when saving changes." msgstr "" -#: ../mugshot/MugshotWindow.py:336 +#: ../mugshot/MugshotWindow.py:339 msgid "User details were not updated." msgstr "" -#: ../mugshot/MugshotWindow.py:491 +#: ../mugshot/MugshotWindow.py:440 msgid "Update Pidgin buddy icon?" msgstr "" -#: ../mugshot/MugshotWindow.py:492 +#: ../mugshot/MugshotWindow.py:441 msgid "Would you also like to update your Pidgin buddy icon?" msgstr "" -#: ../mugshot/MugshotWindow.py:582 +#: ../mugshot/MugshotWindow.py:554 msgid "Enter your password to change user details." msgstr "" -#: ../mugshot/MugshotWindow.py:584 +#: ../mugshot/MugshotWindow.py:556 msgid "" "This is a security measure to prevent unwanted updates\n" "to your personal information." msgstr "" -#: ../mugshot/MugshotWindow.py:789 +#: ../mugshot/MugshotWindow.py:798 msgid "Update LibreOffice user details?" msgstr "" -#: ../mugshot/MugshotWindow.py:790 +#: ../mugshot/MugshotWindow.py:799 msgid "Would you also like to update your user details in LibreOffice?" msgstr ""