diff --git a/data/ui/MugshotWindow.ui b/data/ui/MugshotWindow.ui
index e58fbde..041cc1e 100644
--- a/data/ui/MugshotWindow.ui
+++ b/data/ui/MugshotWindow.ui
@@ -430,10 +430,10 @@
12
end
-
-
+
gtk-ok
True
- False
+ True
+ True
+ True
True
True
@@ -544,6 +546,7 @@ to your personal information.
False
•
password
+
2
@@ -577,8 +580,8 @@ to your personal information.
- button1
- button2
+ password_cancel
+ password_ok
diff --git a/mugshot/MugshotWindow.py b/mugshot/MugshotWindow.py
index c7d36a5..ea9599e 100644
--- a/mugshot/MugshotWindow.py
+++ b/mugshot/MugshotWindow.py
@@ -29,21 +29,36 @@ from mugshot_lib import Window
from mugshot.AboutMugshotDialog import AboutMugshotDialog
from mugshot.PreferencesMugshotDialog import PreferencesMugshotDialog
+username = os.getenv('USER')
+if not username:
+ username = os.getenv('USERNAME')
+
def which(command):
'''Use the system command which to get the absolute path for the given
command.'''
- return subprocess.Popen(['which', command], stdout=subprocess.PIPE).stdout.read().strip()
+ return subprocess.Popen(['which', command], \
+ stdout=subprocess.PIPE).stdout.read().strip()
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 menu_position(self, menu, data=None, something_else=None):
'''Position a menu at the bottom of its attached widget'''
widget = menu.get_attach_widget()
allocation = widget.get_allocation()
window_pos = widget.get_window().get_position()
+ # Align the left side of the menu with the left side of the button.
x = window_pos[0] + allocation.x
+ # Align the top of the menu with the bottom of the button.
y = window_pos[1] + allocation.y + allocation.height
return (x, y, True)
@@ -58,85 +73,76 @@ class MugshotWindow(Window):
self.AboutDialog = AboutMugshotDialog
self.PreferencesDialog = PreferencesMugshotDialog
- self.updated_image = None
+ # 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_menu.attach_to_widget(self.image_button, detach_cb)
+ # 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.user_image = builder.get_object('user_image')
- self.image_button = builder.get_object('image_button')
- self.image_menu = builder.get_object('image_menu')
- self.image_menu.attach_to_widget(self.image_button, detach_cb)
- self.iconview = builder.get_object('stock_iconview')
- self.stock_browser = builder.get_object('stock_browser')
+ # Stock photo browser
+ self.stock_browser = builder.get_object('stock_browser')
+ self.iconview = builder.get_object('stock_iconview')
+
+ # Populate all of the widgets.
+ self.init_user_details()
+
+ 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.expanduser('~/.face')
if os.path.isfile(face):
self.set_user_image(face)
else:
self.set_user_image(None)
-
- # Code for other initialization actions should be added here.
- self.first_name, self.last_name, self.initials, self.office_phone, \
- self.home_phone = self.get_user_details()
- if self.home_phone == 'none': self.home_phone = ''
- if self.office_phone == 'none': self.office_phone = ''
+ self.updated_image = None
+
+ # Search /etc/passwd for the current user's details.
+ logger.debug('Getting user details from /etc/passwd')
+ for line in open('/etc/passwd', 'r'):
+ if line.startswith(username + ':'):
+ logger.debug('Found details: %s' % line.strip())
+ details = line.split(':')[4]
+ name, office, office_phone, home_phone = details.split(',', 3)
+ break
+
+ # Expand the user's fullname into first, last, and initials.
+ try:
+ first_name, last_name = name.split(' ', 1)
+ initials = first_name[0] + last_name[0]
+ except:
+ first_name = name
+ last_name = ''
+ initials = first_name[0]
+
+ # If the variables are defined as 'none', use blank for cleanliness.
+ if home_phone == 'none': home_phone = ''
+ if office_phone == 'none': office_phone = ''
+
+ # Set the class variables
+ self.first_name = first_name
+ self.last_name = last_name
+ self.initials = initials
+ self.home_phone = home_phone
+ self.office_phone = office_phone
+
+ # Populate the GtkEntries.
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)
- def on_image_from_browse_activate(self, widget):
- chooser = Gtk.FileChooserDialog(_("Select an image"), self, Gtk.FileChooserAction.OPEN, (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK))
- image_filter = Gtk.FileFilter()
- image_filter.set_name('Images')
- image_filter.add_mime_type('image/*')
- chooser.add_filter(image_filter)
- response = chooser.run()
- if response == Gtk.ResponseType.OK:
- self.updated_image = chooser.get_filename()
- self.set_user_image(self.updated_image)
- chooser.hide()
-
- def on_stock_browser_delete_event(self, widget, event):
- widget.hide()
- return True
-
- def on_image_from_stock_activate(self, widget):
- self.load_stock_browser()
- self.stock_browser.show_all()
-
- def on_image_button_clicked(self, widget):
- """When the menu button is clicked, display the appmenu."""
- if widget.get_active():
- self.image_menu.popup(None, None, menu_position,
- self.image_menu, 3,
- Gtk.get_current_event_time())
-
- def on_cancel_button_clicked(self, widget):
- self.destroy()
-
- def on_image_menu_hide(self, widget):
- self.image_button.set_active(False)
-
- def get_finger_details_updated(self):
- if self.first_name != self.first_name_entry.get_text().strip() or \
- self.last_name != self.last_name_entry.get_text().strip() or \
- self.home_phone != self.home_phone_entry.get_text().strip() or \
- self.office_phone != self.office_phone_entry.get_text().strip():
- return True
- return False
-
- def on_apply_button_clicked(self, widget):
- if self.get_finger_details_updated():
- self.save_finger()
- if self.updated_image:
- self.save_image()
-
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:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
scaled = pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER)
@@ -144,10 +150,23 @@ class MugshotWindow(Window):
else:
self.user_image.set_from_icon_name('avatar-default', 128)
+ # = 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 /usr/share/pixmaps/faces.
+ logger.debug("Loading stock browser photos.")
for filename in os.listdir('/usr/share/pixmaps/faces'):
full_path = os.path.join('/usr/share/pixmaps/faces/', filename)
if os.path.isfile(full_path):
@@ -155,53 +174,123 @@ class MugshotWindow(Window):
scaled = pixbuf.scale_simple(90, 90, GdkPixbuf.InterpType.HYPER)
model.append([full_path, scaled])
+ 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 get_user_details(self):
- # Get user finger details from /etc/passwd
- username = os.getenv('USER')
- for line in open('/etc/passwd', 'r'):
- if line.startswith(username + ':'):
- details = line.split(':')[4]
- name, office, office_phone, home_phone = details.split(',', 3)
- try:
- first_name, last_name = name.split(' ', 1)
- initials = first_name[0] + last_name[0]
- except:
- first_name = name
- last_name = ''
- initials = first_name[0]
- return first_name, last_name, initials, office_phone, home_phone
+ # = Image Browser ======================================================== #
+ def on_image_from_browse_activate(self, widget):
+ """Browse for a user profile image."""
+ # Initialize a GtkFileChooserDialog.
+ chooser = Gtk.FileChooserDialog(_("Select an image"), self,
+ Gtk.FileChooserAction.OPEN,
+ (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
+ Gtk.STOCK_OK, Gtk.ResponseType.OK))
+
+ # Add a filter for only image files.
+ image_filter = Gtk.FileFilter()
+ image_filter.set_name('Images')
+ image_filter.add_mime_type('image/*')
+ chooser.add_filter(image_filter)
+ # Run the dialog, grab the filename if confirmed, then hide the dialog.
+ response = chooser.run()
+ if response == Gtk.ResponseType.OK:
+ # Update the user image, store the path for committing later.
+ self.updated_image = chooser.get_filename()
+ logger.debug("Selected %s" % self.updated_image)
+ self.set_user_image(self.updated_image)
+ chooser.hide()
+
+ # = Password Entry ======================================================= #
def get_password(self):
- # Show a password dialog to get password.
+ """Display a password dialog for authenticating to sudo and chfn."""
+ logger.debug("Prompting user for password")
dialog = self.builder.get_object('password_dialog')
entry = self.builder.get_object('password_entry')
response = dialog.run()
dialog.hide()
if response == Gtk.ResponseType.OK:
+ logger.debug("Password entered")
pw = entry.get_text()
entry.set_text('')
return pw
+ logger.debug("Cancelled")
return None
- def get_entry_value(self, entry_widget):
- # Get the text from an entry, changing none to ''
- value = entry_widget.get_text().strip()
- if value.lower() == 'none':
- value = ''
- return value
+ def on_password_entry_activate(self, widget):
+ """On Password Entry activate, click OK."""
+ self.builder.get_object('password_ok').activate()
+
+
+ # = Image Button and Menu ================================================ #
+ def on_image_button_clicked(self, widget):
+ """When the menu button is clicked, display the appmenu."""
+ if widget.get_active():
+ self.image_menu.popup(None, None, menu_position,
+ self.image_menu, 3,
+ Gtk.get_current_event_time())
+
+ def on_image_menu_hide(self, widget):
+ """Untoggle the image button when the menu is hidden."""
+ self.image_button.set_active(False)
+
+ # = Mugshot Window ======================================================= #
+ def on_apply_button_clicked(self, widget):
+ """When the window Apply button is clicked, commit any relevant
+ changes."""
+ if self.get_finger_details_updated():
+ returns = self.save_finger()
+ if len(returns) == 1:
+ # Cancelled, password not entered
+ pass
+ else:
+ if returns[0] == 0:
+ self.first_name, self.last_name, self.initials
+
+ if self.updated_image:
+ self.save_image()
+
+ def on_cancel_button_clicked(self, widget):
+ """When the window cancel button is clicked, close the program."""
+ self.destroy()
+
+
+
+
+
+
+
+ def get_finger_details_updated(self):
+ if self.first_name != self.first_name_entry.get_text().strip() or \
+ self.last_name != self.last_name_entry.get_text().strip() or \
+ self.home_phone != self.home_phone_entry.get_text().strip() or \
+ self.office_phone != self.office_phone_entry.get_text().strip():
+ return True
+ return False
+
+
+
def save_finger(self):
return_codes = []
@@ -215,18 +304,19 @@ class MugshotWindow(Window):
chfn = which('chfn')
# Get each of the updated values.
- first_name = self.get_entry_value(self.first_name_entry)
- last_name = self.get_entry_value(self.last_name_entry)
+ 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 = self.get_entry_value(self.office_phone_entry)
+ office_phone = get_entry_value(self.office_phone_entry)
if office_phone == '':
office_phone = 'none'
- home_phone = self.get_entry_value(self.home_phone_entry)
+ 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.
+ logger.debug('Attempting to set fullname with sudo chfn')
child = pexpect.spawn('sudo %s %s' % (chfn, username))
child.timeout = 5
try:
@@ -238,21 +328,33 @@ class MugshotWindow(Window):
child.sendline('')
except pexpect.TIMEOUT:
# Password was incorrect, or sudo rights not granted
+ logger.debug('Timeout reached, password was incorrect or sudo right not granted.')
pass
child.close()
+ if child.exitstatus == 0:
+ self.first_name = first_name
+ self.last_name = last_name
return_codes.append(child.exitstatus)
+ logger.debug('Attempting to set user details with chfn')
child = pexpect.spawn('chfn')
- child.expect('Password: ')
- child.sendline(password)
- child.expect('Room Number.*:')
- child.sendline('')
- child.expect('Work Phone.*:')
- child.sendline(office_phone)
- child.expect('Home Phone.*:')
- child.sendline(home_phone)
- child.sendline(home_phone)
+ child.timeout = 5
+ try:
+ child.expect(['Password: ', pexpect.EOF])
+ child.sendline(password)
+ child.expect('Room Number.*:')
+ child.sendline('')
+ child.expect('Work Phone.*:')
+ child.sendline(office_phone)
+ child.expect('Home Phone.*:')
+ child.sendline(home_phone)
+ child.sendline(home_phone)
+ except pexpect.TIMEOUT:
+ logger.debug('Timeout reached, password was likely incorrect.')
child.close(True)
+ if child.exitstatus == 0:
+ self.office_phone = office_phone
+ self.home_phone = home_phone
return_codes.append(child.exitstatus)
return return_codes