#!/usr/bin/python3 # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- # Mugshot - Lightweight user configuration utility # Copyright (C) 2013-2019 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 locale import gettext as _ import logging import os import shutil import subprocess import pexpect from gi.repository import Gio, Gtk, GdkPixbuf, GLib # pylint: disable=E0611 from mugshot_lib import Window, SudoDialog, AccountsServiceAdapter, helpers try: from mugshot.CameraMugshotDialog import CameraMugshotDialog except: pass logger = logging.getLogger('mugshot') username = GLib.get_user_name() home = GLib.get_home_dir() libreoffice_prefs = os.path.join(GLib.get_user_config_dir(), 'libreoffice', '4', 'user', 'registrymodifications.xcu') pidgin_prefs = os.path.join(home, '.purple', 'prefs.xml') faces_dir = '/usr/share/pixmaps/faces/' def which(command): '''Use the system command which to get the absolute path for the given command.''' command = subprocess.Popen(['which', command], stdout=subprocess.PIPE).stdout.read().strip() command = command.decode('utf-8') if command == '': logger.debug('Command "%s" could not be found.' % command) return None return command def has_running_process(name): """Check for a running process, return True if any listings are found.""" command = 'ps -ef | grep " %s" | grep -v "grep" | wc -l' % name n = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True).stdout.read().strip() return int(n) > 0 def has_gstreamer_camerabin_support(): """Return True if gstreamer1.0 camerabin element is available.""" process = subprocess.Popen(["gst-inspect-1.0", "camerabin"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) process.communicate() has_support = process.returncode == 0 if not has_support: element = 'camerabin' plugin = 'gstreamer1.0-plugins-good' logger.debug('%s element unavailable. ' 'Do you have %s installed?' % (element, plugin)) return has_support def has_gstreamer_camerasrc_support(): """Return True if gstreamer1.0 v4l2src element is available.""" process = subprocess.Popen(["gst-inspect-1.0", "v4l2src"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) process.communicate() has_support = process.returncode == 0 if not has_support: element = 'v4l2src' plugin = 'gstreamer1.0-plugins-good' logger.debug('%s element unavailable. ' 'Do you have %s installed?' % (element, plugin)) return has_support def has_camera_libraries(): """Return True if it is possible to display the camera dialog.""" try: from gi.repository import Cheese, Clutter, GtkClutter # pylint: disable=W0612 except: return False return True def get_camera_installed(): """Return True if /dev/video0 exists.""" if not os.path.exists('/dev/video0'): logger.debug('Camera not detected at /dev/video0') return False return True def get_has_camera_support(): """Return True if cameras are fully supported by this application.""" if not get_camera_installed(): return False if not which('gst-inspect-1.0'): return False if not has_gstreamer_camerabin_support(): return False if not has_gstreamer_camerasrc_support(): return False if not has_camera_libraries(): return False return True def detach_cb(menu, widget): '''Detach a widget from its attached widget.''' menu.detach() def get_entry_value(entry_widget): """Get the value from one of the Mugshot entries.""" # Get the text from an entry, changing none to '' value = entry_widget.get_text().strip() if value.lower() == 'none': value = '' return value def get_confirmation_dialog(parent, primary_message, secondary_message, icon_name=None): """Display a confirmation (yes/no) dialog configured with primary and secondary messages, as well as a custom icon if requested.""" dialog = Gtk.MessageDialog(transient_for=parent, flags=0, message_type=Gtk.MessageType.QUESTION, buttons=Gtk.ButtonsType.YES_NO, text=primary_message) dialog.format_secondary_text(secondary_message) if icon_name: image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.DIALOG) dialog.set_image(image) dialog.show_all() response = dialog.run() dialog.destroy() return response == Gtk.ResponseType.YES # See mugshot_lib.Window.py for more details about how this class works class MugshotWindow(Window): """Mugshot GtkWindow""" __gtype_name__ = "MugshotWindow" def finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(MugshotWindow, self).finish_initializing(builder) self.set_wmclass("Mugshot", "Mugshot") # User Image widgets self.image_button = builder.get_object('image_button') self.user_image = builder.get_object('user_image') self.image_menu = builder.get_object('image_menu') self.image_from_camera = builder.get_object('image_from_camera') self.image_from_stock = builder.get_object('image_from_stock') self.image_from_stock.set_visible(os.path.exists(faces_dir) and len(os.listdir(faces_dir)) > 0) self.menuitem1 = builder.get_object('menuitem1') self.image_remove = builder.get_object('image_remove') if get_has_camera_support(): self.CameraDialog = CameraMugshotDialog self.image_from_camera.set_visible(True) else: self.image_from_camera.set_visible(False) # Entry widgets (chfn) self.first_name_entry = builder.get_object('first_name') self.last_name_entry = builder.get_object('last_name') self.initials_entry = builder.get_object('initials') self.office_phone_entry = builder.get_object('office_phone') self.home_phone_entry = builder.get_object('home_phone') self.email_entry = builder.get_object('email') self.fax_entry = builder.get_object('fax') # Stock photo browser self.stock_browser = builder.get_object('stock_browser') self.iconview = builder.get_object('stock_iconview') # File Chooser Dialog self.chooser = builder.get_object('filechooserdialog') self.crop_center = builder.get_object('crop_center') self.crop_left = builder.get_object('crop_left') self.crop_right = builder.get_object('crop_right') self.file_chooser_preview = builder.get_object('file_chooser_preview') # Add a filter for only image files. image_filter = Gtk.FileFilter() image_filter.set_name('Images') image_filter.add_mime_type('image/*') self.chooser.add_filter(image_filter) self.tmpfile = None self.accounts_service = \ AccountsServiceAdapter.MugshotAccountsServiceAdapter(username) # Users without sudo rights cannot change their name. if not self.accounts_service.available(): self.set_name_editable(SudoDialog.check_dependencies(['chfn'])) self.set_phone_editable(SudoDialog.check_dependencies(['chfn'])) # Populate all of the widgets. self.init_user_details() def set_name_editable(self, editable): """Set name fields editable.""" self.first_name_entry.set_sensitive(editable) self.last_name_entry.set_sensitive(editable) self.initials_entry.set_sensitive(editable) def set_phone_editable(self, editable): self.home_phone_entry.set_sensitive(editable) self.office_phone_entry.set_sensitive(editable) def init_user_details(self): """Initialize the user details entries and variables.""" # 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') 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): try: if os.path.samefile(image, face): self.updated_image = face else: self.updated_image = None except FileNotFoundError: self.updated_image = None self.set_user_image(face) elif os.path.isfile(image): self.updated_image = image self.set_user_image(image) else: self.updated_image = None self.set_user_image(None) user_details = self.get_user_details() # Set the class variables self.first_name = user_details['first_name'] self.last_name = user_details['last_name'] self.initials = user_details['initials'] self.home_phone = user_details['home_phone'] self.office_phone = user_details['office_phone'] self.email = user_details['email'] self.fax = user_details['fax'] # Populate the GtkEntries. logger.debug('Populating entries') self.first_name_entry.set_text(self.first_name) self.last_name_entry.set_text(self.last_name) self.initials_entry.set_text(self.initials) self.office_phone_entry.set_text(self.office_phone) self.home_phone_entry.set_text(self.home_phone) self.email_entry.set_text(self.email) self.fax_entry.set_text(self.fax) # = Mugshot 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 and os.path.exists(filename): try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) scaled = pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER) self.user_image.set_from_pixbuf(scaled) # Show "Remove" menu item. self.menuitem1.set_visible(True) self.image_remove.set_visible(True) return except GLib.Error: # pylint: disable=E0712 pass self.user_image.set_from_icon_name('avatar-default', 128) # Hide "Remove" menu item. self.menuitem1.set_visible(False) self.image_remove.set_visible(False) def suggest_initials(self, first_name, last_name): """Generate initials from first and last name.""" try: initials = first_name[0] + last_name[0] except: if first_name: initials = first_name[0] else: initials = '' return initials def filter_numbers(self, entry, *args): """Allow only numbers and + in phone entry fields.""" text = entry.get_text().strip() entry.set_text(''.join([i for i in text if i in '+0123456789'])) def on_apply_button_clicked(self, widget): """When the window Apply button is clicked, commit any relevant changes.""" logger.debug('Applying changes...') if self.get_chfn_details_updated(): success, response = self.save_chfn_details() if not success: # Password was incorrect, complain. if response in [Gtk.ResponseType.NONE, Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: msg_type = Gtk.MessageType.WARNING primary = _("Authentication cancelled.") elif response == Gtk.ResponseType.REJECT: msg_type = Gtk.MessageType.WARNING primary = _("Authentication failed.") else: msg_type = Gtk.MessageType.ERROR primary = _("An error occurred when saving changes.") secondary = _("User details were not updated.") dialog = Gtk.MessageDialog(transient_for=self, flags=0, message_type=msg_type, buttons=Gtk.ButtonsType.OK, text=primary) dialog.format_secondary_text(secondary) dialog.run() dialog.destroy() return if self.get_as_details_updated(): self.save_as_details() if self.get_libreoffice_details_updated(): self.set_libreoffice_data() if self.updated_image is not None: self.save_image() self.save_gsettings() self.destroy() def save_gsettings(self): """Save details to dconf (the ones not tracked by /etc/passwd)""" logger.debug('Saving details to dconf: /apps/mugshot') self.settings.set_string('initials', get_entry_value(self.initials_entry)) self.settings.set_string('email', get_entry_value(self.email_entry)) self.settings.set_string('fax', get_entry_value(self.fax_entry)) def entry_focus_next(self, widget): """Focus the next available entry when pressing Enter.""" logger.debug('Entry activated, focusing next widget.') vbox = widget.get_parent().get_parent().get_parent().get_parent() vbox.child_focus(Gtk.DirectionType.TAB_FORWARD) def initials_entry_focused(self, widget): """Paste initials into empty field.""" logger.debug('Initials field focused.') first_name = get_entry_value(self.first_name_entry) last_name = get_entry_value(self.last_name_entry) if get_entry_value(self.initials_entry) == '': self.initials_entry.set_text(self.suggest_initials(first_name, last_name)) def on_cancel_button_clicked(self, widget): """When the window cancel button is clicked, close the program.""" logger.debug('Cancel clicked, goodbye.') self.destroy() def on_image_remove_activate(self, widget): """Remove the user's profile image.""" self.updated_image = "" self.set_user_image(None) def on_camera_dialog_apply(self, widget, data=None): """Commit changes when apply is clicked.""" self.updated_image = data self.set_user_image(data) def save_image(self): """Copy the updated image filename to ~/.face""" # Check if the image has been updated. if self.updated_image is None: logger.debug('Photo not updated, not saving changes.') return False if not os.path.isfile(self.updated_image): self.updated_image = "" face = os.path.join(home, '.face') if os.path.normpath(face) != os.path.normpath(self.updated_image): # If the .face file already exists, remove it first. logger.debug('Photo updated, saving ~/.face profile image.') if os.path.isfile(face): os.remove(face) # Copy the new file to ~/.face if os.path.isfile(self.updated_image): shutil.copyfile(self.updated_image, face) # Update AccountsService profile 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) self.updated_image = None return True def set_pidgin_buddyicon(self, filename=None): """Sets the pidgin buddyicon to filename (usually ~/.face). If pidgin is running, use the dbus interface, otherwise directly modify the XML file.""" if not os.path.exists(pidgin_prefs): logger.debug('Pidgin not installed or never opened, not updating.') return logger.debug('Prompting user to update pidgin buddy icon') primary = _("Update Pidgin buddy icon?") secondary = _("Would you also like to update your Pidgin buddy icon?") update_pidgin = get_confirmation_dialog(self, primary, secondary, 'pidgin') if update_pidgin: if has_running_process('pidgin'): self.set_pidgin_buddyicon_dbus(filename) else: self.set_pidgin_buddyicon_xml(filename) else: logger.debug('Reject: Not updating pidgin buddy icon') def set_pidgin_buddyicon_dbus(self, filename=None): """Set the pidgin buddy icon via dbus.""" logger.debug('Updating pidgin buddy icon via dbus') bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) purple = Gio.DBusProxy.new_sync(bus, Gio.DBusProxyFlags.NONE, None, "im.pidgin.purple.PurpleService", "/im/pidgin/purple/PurpleObject", "im.pidgin.purple.PurpleInterface", None) # To make the change instantly visible, set the icon to none first. purple.PurplePrefsSetPath('(ss)', '/pidgin/accounts/buddyicon', '') if filename: purple.PurplePrefsSetPath('(ss)', '/pidgin/accounts/buddyicon', filename) def set_pidgin_buddyicon_xml(self, filename=None): """Set the buddyicon used by pidgin to filename (via the xml file).""" # This is hacky, but a working implementation for now... logger.debug('Updating pidgin buddy icon via xml') prefs_file = pidgin_prefs tmp_buffer = [] if os.path.isfile(prefs_file): for line in open(prefs_file): if '\n' % filename else: new = new + 'value=\'\'/>\n' tmp_buffer.append(new) else: tmp_buffer.append(line) write_prefs = open(prefs_file, 'w') for line in tmp_buffer: write_prefs.write(line) 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 return False 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 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(): modified = True if modified: logger.debug('chfn details have been modified.') return True logger.debug('chfn details have NOT been modified.') return False def process_terminal_password(self, command, password): """Handle password prompts from the interactive chfn commands.""" # Force the C language for guaranteed english strings in the script. logger.debug('Executing: %s' % command) child = SudoDialog.env_spawn(command, [], 5) child.write_to_stdout = True try: child.expect([".*ssword.*", pexpect.EOF]) child.sendline(password) child.expect([pexpect.EOF]) except pexpect.TIMEOUT: logger.warning('Timeout reached, password was likely incorrect.') child.close(True) return child.exitstatus == 0 def save_chfn_details(self): """Commit changes to chfn-related details. For full name, changes must be performed as root. Other changes are done with the user password. Return TRUE if successful.""" success = True # Get the password for sudo sudo_dialog = SudoDialog.SudoDialog( icon=None, name=_("Mugshot"), retries=3) sudo_dialog.format_primary_text(_("Enter your password to change user " "details.")) sudo_dialog.format_secondary_text(_("This is a security measure to " "prevent unwanted updates\n" "to your personal information.")) response = sudo_dialog.run() sudo_dialog.hide() password = sudo_dialog.get_password() sudo_dialog.destroy() if not password: return (False, response) sudo = which('sudo') chfn = which('chfn') # Get each of the updated values. 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() office_phone = get_entry_value(self.office_phone_entry) if office_phone == '': office_phone = 'none' home_phone = get_entry_value(self.home_phone_entry) if home_phone == '': home_phone = 'none' # Full name can only be modified by root. Try using sudo to modify. 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) if self.process_terminal_password(command, password): self.home_phone = home_phone else: success = False logger.debug('Updating Office Phone...') # chfn 2.29 uses "-p" as parameter for changing the office-phone (LP: #1699285) p_command = "%s -p \"%s\" %s" % (chfn, office_phone, username) # other (newer, older?) use "-w" w_command = "%s -w \"%s\" %s" % (chfn, office_phone, username) if self.process_terminal_password(p_command, password) or \ self.process_terminal_password(w_command, password): self.office_phone = office_phone else: success = False return (success, response) # = LibreOffice ========================================================= # def get_libreoffice_details_updated(self): """Return True if LibreOffice settings need to be updated.""" # Return False if there is no preferences file. if not os.path.isfile(libreoffice_prefs): logger.debug('LibreOffice is not installed or has not been opened.' ' Not updating.') return False # Compare the current entries to the existing LibreOffice data. data = self.get_libreoffice_data() if data['first_name'] != get_entry_value(self.first_name_entry): return True if data['last_name'] != get_entry_value(self.last_name_entry): return True if data['initials'] != get_entry_value(self.initials_entry): return True if data['email'] != get_entry_value(self.email_entry): return True if data['home_phone'] != get_entry_value(self.home_phone_entry): return True if data['office_phone'] != get_entry_value(self.office_phone_entry): return True if data['fax'] != get_entry_value(self.fax_entry): return True logger.debug('LibreOffice details do not need to be updated.') return False def get_user_details(self): """Use the various methods to get the most up-to-date version of the user details.""" # Start with LibreOffice, as users may have configured that first. data = self.get_libreoffice_data() # Prefer AccountsService, GLib, then passwd as_data = self.get_accounts_service_data() gl_data = self.get_glib_data() pwd_data = self.get_passwd_data() 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 if len(self.settings['initials']) > 0: data['initials'] = self.settings['initials'] if len(data['email']) == 0: 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() if name: name = self.split_name(name) else: name = {'first': '', 'last': '', 'initials': ''} email = self.accounts_service.get_email() if not email: 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': ''} return data def get_passwd_data(self): """Get user details from passwd""" # Use getent for current user's details. try: line = subprocess.check_output(['getent', 'passwd', username]) if isinstance(line, bytes): line = line.decode('utf-8') line = line.strip() logger.debug('Found details: %s' % line.strip()) details = line.split(':')[4].split(',') while len(details) < 4: details.append("") # Extract the user details name = details[0] office_phone = details[2] home_phone = details[3] except subprocess.CalledProcessError: logger.warning("User %s not found in /etc/passwd. " "Mugshot may not function correctly." % username) name = "" office_phone = "" home_phone = "" name = self.split_name(name) # If the variables are defined as 'none', use blank for cleanliness. if home_phone == 'none': home_phone = '' if office_phone == 'none': office_phone = '' # Pack the data data = {'first_name': name['first'], 'last_name': name['last'], 'home_phone': home_phone, 'office_phone': office_phone, 'initials': name['initials'], 'email': '', 'fax': ''} return data def get_libreoffice_data(self): """Get each of the preferences from the LibreOffice registymodifications preferences file. Return a dict with the details.""" prefs_file = libreoffice_prefs data = {'first_name': '', 'last_name': '', 'initials': '', 'email': '', 'home_phone': '', 'office_phone': '', 'fax': ''} if os.path.isfile(prefs_file): logger.debug('Getting settings from %s' % prefs_file) # Check for file access try: prefs = open(prefs_file, 'r') prefs.close() except PermissionError: logger.debug('Reject: Cannot open file.') return data for line in open(prefs_file): if "UserProfile/Data" in line: try: value = line.split('')[1].split('')[0] except IndexError: continue value = value.strip() # First Name if 'name="givenname"' in line: data['first_name'] = value # Last Name elif 'name="sn"' in line: data['last_name'] = value # Initials elif 'name="initials"' in line: data['initials'] = value # Email elif 'name="mail"' in line: data['email'] = value # Home Phone elif 'name="homephone"' in line: data['home_phone'] = value # Office Phone elif 'name="telephonenumber"' in line: data['office_phone'] = value # Fax Number elif 'name="facsimiletelephonenumber"' in line: data['fax'] = value else: pass return data def set_libreoffice_data(self): """Update the LibreOffice registymodifications preferences file.""" prefs_file = libreoffice_prefs if os.path.isfile(prefs_file): logger.debug('Prompting user to update LibreOffice details.') update_libreoffice = \ get_confirmation_dialog(self, _("Update LibreOffice user details?"), _("Would you also like to update your " "user details in LibreOffice?"), 'libreoffice-startcenter') if update_libreoffice: logger.debug('Confirm: Updating details.') first_name = get_entry_value(self.first_name_entry) first_name_updated = False last_name = get_entry_value(self.last_name_entry) last_name_updated = False initials = get_entry_value(self.initials_entry) initials_updated = False email = get_entry_value(self.email_entry) email_updated = False home_phone = get_entry_value(self.home_phone_entry) home_phone_updated = False office_phone = get_entry_value(self.office_phone_entry) office_phone_updated = False fax = get_entry_value(self.fax_entry) fax_updated = False tmp_buffer = [] # Check for file access try: prefs = open(prefs_file, 'a') prefs.close() except PermissionError: logger.debug('Reject: Not updating.') return for line in open(prefs_file): new = None if "UserProfile/Data" in line: new = line.split('')[0] # First Name if 'name="givenname"' in line: new = new + '%s\n' % \ first_name first_name_updated = True # Last Name elif 'name="sn"' in line: new = new + '%s\n' % \ last_name last_name_updated = True # Initials elif 'name="initials"' in line: new = new + '%s\n' % \ initials initials_updated = True # Email elif 'name="mail"' in line: new = new + '%s\n' % \ email email_updated = True # Home Phone elif 'name="homephone"' in line: new = new + '%s\n' % \ home_phone home_phone_updated = True # Office Phone elif 'name="telephonenumber"' in line: new = new + '%s\n' % \ office_phone office_phone_updated = True # Fax Number elif 'name="facsimiletelephonenumber"' in line: new = new + '%s\n' % \ fax fax_updated = True else: new = line tmp_buffer.append(new) elif '' in line: pass else: tmp_buffer.append(line) open_prefs = open(prefs_file, 'w') for line in tmp_buffer: open_prefs.write(line) if not first_name_updated: string = \ '' '' '%s\n' % first_name open_prefs.write(string) if not last_name_updated: string = \ '' '' '%s\n' % last_name open_prefs.write(string) if not initials_updated: string = \ '' '' '%s\n' % initials open_prefs.write(string) if not email_updated: string = \ '' '' '%s\n' % email open_prefs.write(string) if not home_phone_updated: string = \ '' '' '%s\n' % home_phone open_prefs.write(string) if not office_phone_updated: string = \ '' '' '%s\n' % office_phone open_prefs.write(string) if not fax_updated: string = \ '' '' '%s\n' % fax open_prefs.write(string) open_prefs.write('') open_prefs.close() else: logger.debug('Reject: Not updating.') # = Stock Browser ======================================================= # def on_image_from_stock_activate(self, widget): """When the 'Select image from stock' menu item is clicked, load and display the stock photo browser.""" self.load_stock_browser() self.stock_browser.show_all() def load_stock_browser(self): """Load the stock photo browser.""" # Check if the photos have already been loaded. model = self.iconview.get_model() if len(model) != 0: logger.debug("Stock browser already loaded.") return # If they have not, load each photo from faces_dir. logger.debug("Loading stock browser photos.") for filename in os.listdir(faces_dir): full_path = os.path.join(faces_dir, filename) if os.path.isfile(full_path): pixbuf = GdkPixbuf.Pixbuf.new_from_file(full_path) scaled = pixbuf.scale_simple(90, 90, GdkPixbuf.InterpType.HYPER) model.append([full_path, scaled]) def on_stock_iconview_selection_changed(self, widget): """Enable stock submission only when an item is selected.""" selected_items = self.iconview.get_selected_items() is_sensitive = len(selected_items) > 0 self.builder.get_object('stock_ok').set_sensitive(is_sensitive) def on_stock_browser_delete_event(self, widget, event): """Hide the stock browser instead of deleting it.""" widget.hide() return True def on_stock_cancel_clicked(self, widget): """Hide the stock browser when Cancel is clicked.""" self.stock_browser.hide() def on_stock_ok_clicked(self, widget): """When the stock browser OK button is clicked, get the currently selected photo and set it to the user profile image.""" selected_items = self.iconview.get_selected_items() if len(selected_items) != 0: # Get the filename from the stock browser iconview. path = int(selected_items[0].to_string()) filename = self.iconview.get_model()[path][0] logger.debug("Selected %s" % filename) # Update variables and widgets, then hide. self.set_user_image(filename) self.updated_image = filename self.stock_browser.hide() def on_stock_iconview_item_activated(self, widget, path): """Allow selecting a stock photo with Enter.""" self.on_stock_ok_clicked(widget) # = Image Browser ======================================================= # def on_image_from_browse_activate(self, widget): """Browse for a user profile image.""" # Run the dialog, grab the filename if confirmed, then hide the dialog. response = self.chooser.run() if response == Gtk.ResponseType.APPLY: # Update the user image, store the path for committing later. self.updated_image = helpers.new_tempfile('browse') self.filechooser_preview_pixbuf.savev(self.updated_image, "png", [], []) logger.debug("Selected %s" % self.updated_image) self.set_user_image(self.updated_image) self.chooser.hide() def on_filechooserdialog_update_preview(self, widget): """Update the preview image used in the file chooser.""" filename = widget.get_filename() if not filename: self.file_chooser_preview.set_from_icon_name('folder', 128) return if not os.path.isfile(filename): self.file_chooser_preview.set_from_icon_name('folder', 128) return filechooser_pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) # Get the image dimensions. height = filechooser_pixbuf.get_height() width = filechooser_pixbuf.get_width() start_x = 0 start_y = 0 if self.crop_center.get_active(): # Calculate a balanced center. if width > height: start_x = (width - height) / 2 width = height else: start_y = (height - width) / 2 height = width elif self.crop_left.get_active(): start_x = 0 if width > height: width = height else: start_y = (height - width) / 2 height = width elif self.crop_right.get_active(): if width > height: start_x = width - height width = height else: start_y = (height - width) / 2 height = width # Create a new cropped pixbuf. self.filechooser_preview_pixbuf = \ filechooser_pixbuf.new_subpixbuf(start_x, start_y, width, height) scaled = self.filechooser_preview_pixbuf.scale_simple( 128, 128, GdkPixbuf.InterpType.HYPER) self.file_chooser_preview.set_from_pixbuf(scaled) def on_crop_changed(self, widget, data=None): """Update the preview image when crop style is modified.""" if widget.get_active(): self.on_filechooserdialog_update_preview(self.chooser)