commit before release

This commit is contained in:
Sean Davis 2013-07-17 12:53:18 -04:00
parent 02b24aa5e6
commit 0ec39ba219
14 changed files with 1774 additions and 5 deletions

View File

@ -2,3 +2,5 @@ project = mugshot
version = 12.08.1
template = ubuntu-application
dependencies =
lp_id = mugshot
ppa = mugshot-ppa

View File

@ -0,0 +1,343 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
from locale import gettext as _
import logging
logger = logging.getLogger('mugshot')
from gi.repository import Gtk, GdkX11, GObject, Gst, GstVideo, GdkPixbuf
import cairo
import tempfile, os
from mugshot_lib.CameraDialog import CameraDialog
class CameraMugshotDialog(CameraDialog):
__gtype_name__ = "CameraMugshotDialog"
def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the camera dialog"""
super(CameraMugshotDialog, self).finish_initializing(builder)
# Initialize Gst or nothing will work.
Gst.init(None)
# Pack the video widget into the dialog.
vbox = builder.get_object('camera_box')
self.video_window = Gtk.DrawingArea()
self.video_window.connect("realize",self.__on_video_window_realized)
vbox.pack_start(self.video_window, True, True, 0)
self.video_window.show()
# Prepare the camerabin element.
self.camerabin = Gst.ElementFactory.make("camerabin", "camera-source")
if self.camerabin:
bus = self.camerabin.get_bus()
bus.add_signal_watch()
bus.enable_sync_message_emission()
bus.connect("message", self._on_message)
bus.connect("sync-message::element", self._on_sync_message)
self.realized = False
self.draw_handler = self.video_window.connect('draw', self.on_draw)
# If the camera fails to load, show an error on the screen.
else:
devices = []
for device in os.listdir('/dev/'):
if device.startswith('video'): devices.append(device)
logger.error(_('Camera failed to load. Devices: %s') % '; '.join(devices))
self.draw_handler = self.video_window.connect('draw', self.on_failed_draw)
self.realized = True
# Essential widgets
self.record_button = builder.get_object('camera_record')
self.apply_button = builder.get_object('camera_apply')
# Store the temporary filename to be used.
self.filename = None
self.show_all()
def on_failed_draw(self, widget, ctx):
"""Display a message that the camera failed to load."""
# Get the height and width of the drawing area.
alloc = widget.get_allocation()
height = alloc.height
width = alloc.width
# Make the background black.
ctx.set_source_rgb(0,0,0)
ctx.paint()
# Set the font details.
font_size = 20
font_color = (255,255,255)
font_name = "Sans"
# Translators: Please include the newline, required to fit the message.
message = _("Sorry, but your camera\nfailed to initialize.")
# Draw the message to the drawing area.
ctx.set_source_rgb(*font_color)
ctx.select_font_face(font_name, cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(font_size)
ctx.move_to(10,(height-font_size)/2)
ctx.show_text(message.split('\n')[0])
ctx.move_to(10,(height-font_size)/2+font_size)
ctx.show_text(message.split('\n')[1])
def on_draw(self, widget, ctx):
"""Display a message that the camera is initializing on first draw.
Afterwards, blank the drawing area to clear the message."""
# Get the height and width of the drawing area.
alloc = widget.get_allocation()
height = alloc.height
width = alloc.width
# Set the font details.
font_size = 20
font_color = (255,255,255)
font_name = "Sans"
# Translators: Please include the newline, required to fit the message.
message = _("Please wait while your\ncamera is initialized.")
# Draw the message to the drawing area.
ctx.set_source_rgb(*font_color)
ctx.select_font_face(font_name, cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(font_size)
ctx.move_to(10,(height-font_size)/2)
ctx.show_text(message.split('\n')[0])
ctx.move_to(10,(height-font_size)/2+font_size)
ctx.show_text(message.split('\n')[1])
# Redefine on_draw to blank the drawing area next time.
def on_draw(self, widget, ctx):
ctx.set_source_rgb(0,0,0)
ctx.paint()
# Redefine on_draw once more to do nothing else.
def on_draw(self, widget, ctx):
pass
def play(self):
"""Start the camera streaming and display the output. It is necessary
to start the camera playing before using most other functions."""
if not self.realized:
self._set_video_window_id()
if not self.realized:
logger.error(_("Cannot display camera output. Ignoring play command"))
else:
if self.camerabin:
self.camerabin.set_state(Gst.State.PLAYING)
def pause(self):
"""Pause the camera output. It will cause the image to "freeze".
Use play() to start the camera playing again. Note that calling pause
before play may cause errors on certain camera."""
if self.camerabin:
self.camerabin.set_state(Gst.State.PAUSED)
def take_picture(self, filename):
"""take_picture - grab a frame from the webcam and save it to
'filename.
If play is not called before take_picture,
an error may occur. If take_picture is called immediately after play,
the camera may not be fully initialized, and an error may occur.
Connect to the signal "image-captured" to be alerted when the picture
is saved."""
self.camerabin.set_property("location", filename)
self.camerabin.emit("start-capture")
def stop(self):
"""Stop the camera streaming and display the output."""
self.camerabin.set_state(Gst.State.NULL)
def _on_message(self, bus, message):
"""Internal signal handler for bus messages.
May be useful to extend in a base class to handle messages
produced from custom behaviors.
arguments -
bus: the bus from which the message was sent, typically self.bux
message: the message sent"""
# Ignore if there is no message.
if message is None:
return
# Get the message type.
t = message.type
# Initial load, wait until camera is ready before enabling capture.
if t == Gst.MessageType.ASYNC_DONE:
self.record_button.set_sensitive(True)
if t == Gst.MessageType.ELEMENT:
# Keep the camera working after several pictures are taken.
if message.get_structure().get_name() == "image-captured":
self.camerabin.set_state(Gst.Sate.NULL)
self.camerabin.set_state(Gst.State.PLAYING)
self.emit("image-captured", self.filename)
# Enable interface elements once the images are finished saving.
elif message.get_structure().get_name() == "image-done":
self.apply_button.set_sensitive(True)
self.record_button.set_sensitive(True)
self.pause()
# Stop the stream if the EOS (end of stream) message is received.
if t == Gst.MessageType.EOS:
self.camerabin.set_state(Gst.State.NULL)
# Capture and report any error received.
elif t == Gst.MessageType.ERROR:
err, debug = message.parse_error()
logger.error("%s" % err, debug)
def _on_sync_message(self, bus, message):
""" _on_sync_message - internal signal handler for bus messages.
May be useful to extend in a base class to handle messages
produced from custom behaviors.
arguments -
bus: the bus from which the message was sent, typically self.bux
message: the message sent
"""
# Ignore empty messages.
if message.get_structure() is None:
return
message_name = message.get_structure().get_name()
# Embed the gstreamer element into our window.
if message_name == "prepare-window-handle":
imagesink = message.src
imagesink.set_property("force-aspect-ratio", True)
imagesink.set_window_handle(self.video_window.get_window().get_xid())
def __on_video_window_realized(self, widget, data=None):
"""Internal signal handler, used to set up the xid for the drawing area
in a thread safe manner. Do not call directly."""
self._set_video_window_id()
def _set_video_window_id(self):
"""Set the window ID only if not previously configured."""
if not self.realized and self.video_window.get_window() is not None:
x = self.video_window.get_window().get_xid()
self.realized = True
def on_camera_record_clicked(self, widget):
"""When the camera record/retry button is clicked:
Record: Pause the video, start the capture, enable apply and retry.
Retry: Restart the video stream."""
# Remove any previous temporary file.
if self.filename and os.path.isfile(self.filename):
os.remove(self.filename)
# Retry action.
if self.apply_button.get_sensitive():
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
self.apply_button.set_sensitive(False)
self.play()
# Record (Capture) action.
else:
# Create a new temporary file.
tmpfile = tempfile.NamedTemporaryFile(delete=False)
tmpfile.close()
self.filename = tmpfile.name
# Capture the current image.
self.take_picture(self.filename)
# Set the record button to retry, and disable it until the capture
# finishes.
self.record_button.set_label(_("Retry"))
self.record_button.set_sensitive(False)
def on_camera_apply_clicked(self, widget):
"""When the camera Apply button is clicked, crop the current photo and
emit a signal to let the main application know there is a new file
available. Then close the camera dialog."""
self.center_crop(self.filename)
self.emit("apply", self.filename)
self.hide()
def on_camera_cancel_clicked(self, widget):
"""When the Cancel button is clicked, just hide the dialog."""
self.hide()
def on_camera_mugshot_dialog_destroy(self, widget, data=None):
"""When the application exits, remove the current temporary file and
stop the gstreamer element."""
# Clear away the temp file.
if self.filename and os.path.isfile(self.filename):
os.remove(self.filename)
# Clean up the camera before exiting
self.camerabin.set_state(Gst.State.NULL)
def on_camera_mugshot_dialog_hide(self, widget, data=None):
"""When the dialog is hidden, pause the camera recording."""
self.pause()
def on_camera_mugshot_dialog_show(self, widget, data=None):
"""When the dialog is shown, set the record button to record, disable
the apply button, and start the camera."""
self.record_button.set_label(Gtk.STOCK_MEDIA_RECORD)
self.apply_button.set_sensitive(False)
self.show_all()
self.play()
def center_crop(self, filename):
"""Crop the specified file to square dimensions."""
# Load the image into a Pixbuf.
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
# Get the image dimensions.
height = pixbuf.get_height()
width = pixbuf.get_width()
start_x = 0
start_y = 0
# Calculate a balanced center.
if width > height:
start_x = (width-height)/2
width = height
else:
start_y = (height-width)/2
height = width
# Create a new cropped pixbuf.
new_pixbuf = pixbuf.new_subpixbuf(start_x, start_y, width, height)
# Overwrite the temporary file with our new cropped image.
new_pixbuf.savev(filename, "png", [], [])
def on_camera_mugshot_dialog_delete_event(self, widget, data=None):
"""Override the dialog delete event to just hide the window."""
self.hide()
return True
# Signals used by CameraMugshotDialog:
# image-captured: emitted when the camera is done capturing an image.
# apply: emitted when the apply button has been pressed and there is a new file saved for use.
__gsignals__ = {'image-captured' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE,
(GObject.TYPE_PYOBJECT,)),
'apply' : (GObject.SIGNAL_RUN_LAST, GObject.TYPE_NONE, (GObject.TYPE_STRING,))
}

View File

@ -0,0 +1,645 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
from locale import gettext as _
import os
# Used for automating chfn
import pexpect
# Used for copying files to ~/.face
import shutil
# Used for which command and checking for running processes.
import subprocess
# DBUS interface is used to update pidgin buddyicon when pidgin is running.
import dbus
from gi.repository import Gtk, Gdk, GdkPixbuf # pylint: disable=E0611
import logging
logger = logging.getLogger('mugshot')
from mugshot_lib import Window
from mugshot.CameraMugshotDialog import CameraMugshotDialog
username = os.getenv('USER')
if not username:
username = os.getenv('USERNAME')
home = os.path.expanduser('~')
libreoffice_prefs = os.path.join(home, '.config', '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.'''
return subprocess.Popen(['which', command], \
stdout=subprocess.PIPE).stdout.read().strip()
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 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(parent, flags=0, type=Gtk.MessageType.QUESTION,
buttons=Gtk.ButtonsType.YES_NO,
message_format=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
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)
# See mugshot_lib.Window.py for more details about how this class works
class MugshotWindow(Window):
__gtype_name__ = "MugshotWindow"
def finish_initializing(self, builder): # pylint: disable=E1002
"""Set up the main window"""
super(MugshotWindow, self).finish_initializing(builder)
self.CameraDialog = CameraMugshotDialog
# 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)
self.image_from_camera = builder.get_object('image_from_camera')
image_from_browse = builder.get_object('image_from_browse')
image_from_browse.set_visible( os.path.exists(faces_dir) and \
len(os.listdir(faces_dir)) > 0 )
# 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')
# 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)
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 = ''
# Get dconf settings
logger.debug('Getting initials, email, and fax from dconf')
if self.settings['initials'] != '':
initials = self.settings['initials']
email = self.settings['email']
fax = self.settings['fax']
# 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.
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(email)
self.fax_entry.set_text(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:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename)
scaled = pixbuf.scale_simple(128, 128, GdkPixbuf.InterpType.HYPER)
self.user_image.set_from_pixbuf(scaled)
else:
self.user_image.set_from_icon_name('avatar-default', 128)
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():
returns = self.save_chfn_details()
if self.get_libreoffice_details_updated():
self.set_libreoffice_data()
if self.updated_image:
self.save_image()
self.save_gsettings()
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 on_cancel_button_clicked(self, widget):
"""When the window cancel button is clicked, close the program."""
logger.debug('Cancel clicked, goodbye.')
self.destroy()
# = Image Button and Menu ================================================ #
def on_image_button_clicked(self, widget):
"""When the menu button is clicked, display the photo menu."""
logger.debug('Show photo menu')
self.image_from_camera.set_visible(os.path.exists('/dev/video0'))
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)
def on_camera_dialog_apply(self, widget, data=None):
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 not self.updated_image:
logger.debug('Photo not updated, not saving changes.')
return False
face = os.path.expanduser('~/.face')
# If the .face file already exists, remove it first.
logger.debug('Photo updated, saving changes.')
if os.path.isfile(face):
os.remove(face)
# Copy the new file to ~/.face
shutil.copyfile(self.updated_image, face)
self.set_pidgin_buddyicon(face)
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')
update_pidgin = get_confirmation_dialog(self,
_("Update Pidgin Buddy Icon?"),
_("Would you also like to update your Pidgin buddy icon?"),
'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 = dbus.SessionBus()
obj = bus.get_object("im.pidgin.purple.PurpleService",
"/im/pidgin/purple/PurpleObject")
purple = dbus.Interface(obj, "im.pidgin.purple.PurpleInterface")
# To make the change instantly visible, set the icon to none first.
purple.PurplePrefsSetPath('/pidgin/accounts/buddyicon', '')
if filename:
purple.PurplePrefsSetPath('/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 '<pref name=\'buddyicon\'' in line:
new = line.split('value=')[0]
if filename:
new = new + 'value=\'%s\'/>\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_chfn_details_updated(self):
"""Return True if chfn-related details have been modified."""
logger.debug('Checking if chfn details have been modified.')
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():
logger.debug('chfn details have been modified.')
return True
logger.debug('chfn details have NOT been modified.')
return False
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 exit codes for 1) full name changes and 2) home/work phone
changes.
e.g. [0, 0] (both passed)"""
return_codes = []
# Get the user's password
password = self.get_password()
if not password:
return return_codes
username = os.getenv('USER')
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.
logger.debug('Attempting to set fullname with sudo chfn')
child = pexpect.spawn('sudo %s %s' % (chfn, username))
child.timeout = 5
try:
child.expect([".*ssword.*", pexpect.EOF])
child.sendline(password)
child.expect("Full Name.*:")
child.sendline(full_name)
for i in range(5):
child.sendline('')
except pexpect.TIMEOUT:
# Password was incorrect, or sudo rights not granted
logger.debug('Timeout reached, password was incorrect or sudo ' \
'rights 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.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
# = 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_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)
for line in open(prefs_file):
if "UserProfile/Data" in line:
value = line.split('<value>')[1].split('</value>')[0]
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.')
tmp_buffer = []
for line in open(prefs_file):
new = None
if "UserProfile/Data" in line:
new = line.split('<value>')[0]
# First Name
if 'name="givenname"' in line:
new = new + '<value>%s</value></prop></item>\n' % \
get_entry_value(self.first_name_entry)
# Last Name
elif 'name="sn"' in line:
new = new + '<value>%s</value></prop></item>\n' % \
get_entry_value(self.last_name_entry)
# Initials
elif 'name="initials"' in line:
new = new + '<value>%s</value></prop></item>\n' % \
get_entry_value(self.initials_entry)
# Email
elif 'name="mail"' in line:
new = new + '<value>%s</value></prop></item>\n' % \
get_entry_value(self.email_entry)
# Home Phone
elif 'name="homephone"' in line:
new = new + '<value>%s</value></prop></item>\n' % \
get_entry_value(self.home_phone_entry)
# Office Phone
elif 'name="telephonenumber"' in line:
new = new + '<value>%s</value></prop></item>\n' % \
get_entry_value(self.office_phone_entry)
# Fax Number
elif 'name="facsimiletelephonenumber"' in line:
new = new + '<value>%s</value></prop></item>\n' % \
get_entry_value(self.fax_entry)
else:
new = line
tmp_buffer.append(new)
else:
tmp_buffer.append(line)
open_prefs = open(prefs_file, 'w')
for line in tmp_buffer:
open_prefs.write(line)
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 /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):
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()
self.builder.get_object('stock_ok').set_sensitive(
len(selected_items) > 0)
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."""
# 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):
"""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 on_password_entry_changed(self, widget):
"""Enable password submission only when password is not blank."""
self.builder.get_object('password_ok').set_sensitive(
len(widget.get_text()) > 0)

View File

@ -0,0 +1,44 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
import optparse
from locale import gettext as _
from gi.repository import Gtk # pylint: disable=E0611
from mugshot import MugshotWindow
from mugshot_lib import set_up_logging, get_version
def parse_options():
"""Support for command line options"""
parser = optparse.OptionParser(version="%%prog %s" % get_version())
parser.add_option(
"-v", "--verbose", action="count", dest="verbose",
help=_("Show debug messages (-vv debugs mugshot_lib also)"))
(options, args) = parser.parse_args()
set_up_logging(options)
def main():
'constructor for your class instances'
parse_options()
# Run the application.
window = MugshotWindow.MugshotWindow()
window.show()
Gtk.main()

View File

@ -0,0 +1,325 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
'''Enhances builder connections, provides object to access glade objects'''
from gi.repository import GObject, Gtk # pylint: disable=E0611
import inspect
import functools
import logging
logger = logging.getLogger('mugshot_lib')
from xml.etree.cElementTree import ElementTree
# this module is big so uses some conventional prefixes and postfixes
# *s list, except self.widgets is a dictionary
# *_dict dictionary
# *name string
# ele_* element in a ElementTree
# pylint: disable=R0904
# the many public methods is a feature of Gtk.Builder
class Builder(Gtk.Builder):
''' extra features
connects glade defined handler to default_handler if necessary
auto connects widget to handler with matching name or alias
auto connects several widgets to a handler via multiple aliases
allow handlers to lookup widget name
logs every connection made, and any on_* not made
'''
def __init__(self):
Gtk.Builder.__init__(self)
self.widgets = {}
self.glade_handler_dict = {}
self.connections = []
self._reverse_widget_dict = {}
# pylint: disable=R0201
# this is a method so that a subclass of Builder can redefine it
def default_handler(self,
handler_name, filename, *args, **kwargs):
'''helps the apprentice guru
glade defined handlers that do not exist come here instead.
An apprentice guru might wonder which signal does what he wants,
now he can define any likely candidates in glade and notice which
ones get triggered when he plays with the project.
this method does not appear in Gtk.Builder'''
logger.debug('''tried to call non-existent function:%s()
expected in %s
args:%s
kwargs:%s''', handler_name, filename, args, kwargs)
# pylint: enable=R0201
def get_name(self, widget):
''' allows a handler to get the name (id) of a widget
this method does not appear in Gtk.Builder'''
return self._reverse_widget_dict.get(widget)
def add_from_file(self, filename):
'''parses xml file and stores wanted details'''
Gtk.Builder.add_from_file(self, filename)
# extract data for the extra interfaces
tree = ElementTree()
tree.parse(filename)
ele_widgets = tree.getiterator("object")
for ele_widget in ele_widgets:
name = ele_widget.attrib['id']
widget = self.get_object(name)
# populate indexes - a dictionary of widgets
self.widgets[name] = widget
# populate a reversed dictionary
self._reverse_widget_dict[widget] = name
# populate connections list
ele_signals = ele_widget.findall("signal")
connections = [
(name,
ele_signal.attrib['name'],
ele_signal.attrib['handler']) for ele_signal in ele_signals]
if connections:
self.connections.extend(connections)
ele_signals = tree.getiterator("signal")
for ele_signal in ele_signals:
self.glade_handler_dict.update(
{ele_signal.attrib["handler"]: None})
def connect_signals(self, callback_obj):
'''connect the handlers defined in glade
reports successful and failed connections
and logs call to missing handlers'''
filename = inspect.getfile(callback_obj.__class__)
callback_handler_dict = dict_from_callback_obj(callback_obj)
connection_dict = {}
connection_dict.update(self.glade_handler_dict)
connection_dict.update(callback_handler_dict)
for item in connection_dict.items():
if item[1] is None:
# the handler is missing so reroute to default_handler
handler = functools.partial(
self.default_handler, item[0], filename)
connection_dict[item[0]] = handler
# replace the run time warning
logger.warn("expected handler '%s' in %s",
item[0], filename)
# connect glade define handlers
Gtk.Builder.connect_signals(self, connection_dict)
# let's tell the user how we applied the glade design
for connection in self.connections:
widget_name, signal_name, handler_name = connection
logger.debug("connect builder by design '%s', '%s', '%s'",
widget_name, signal_name, handler_name)
def get_ui(self, callback_obj=None, by_name=True):
'''Creates the ui object with widgets as attributes
connects signals by 2 methods
this method does not appear in Gtk.Builder'''
result = UiFactory(self.widgets)
# Hook up any signals the user defined in glade
if callback_obj is not None:
# connect glade define handlers
self.connect_signals(callback_obj)
if by_name:
auto_connect_by_name(callback_obj, self)
return result
# pylint: disable=R0903
# this class deliberately does not provide any public interfaces
# apart from the glade widgets
class UiFactory():
''' provides an object with attributes as glade widgets'''
def __init__(self, widget_dict):
self._widget_dict = widget_dict
for (widget_name, widget) in widget_dict.items():
setattr(self, widget_name, widget)
# Mangle any non-usable names (like with spaces or dashes)
# into pythonic ones
cannot_message = """cannot bind ui.%s, name already exists
consider using a pythonic name instead of design name '%s'"""
consider_message = """consider using a pythonic name instead of design name '%s'"""
for (widget_name, widget) in widget_dict.items():
pyname = make_pyname(widget_name)
if pyname != widget_name:
if hasattr(self, pyname):
logger.debug(cannot_message, pyname, widget_name)
else:
logger.debug(consider_message, widget_name)
setattr(self, pyname, widget)
def iterator():
'''Support 'for o in self' '''
return iter(widget_dict.values())
setattr(self, '__iter__', iterator)
def __getitem__(self, name):
'access as dictionary where name might be non-pythonic'
return self._widget_dict[name]
# pylint: enable=R0903
def make_pyname(name):
''' mangles non-pythonic names into pythonic ones'''
pyname = ''
for character in name:
if (character.isalpha() or character == '_' or
(pyname and character.isdigit())):
pyname += character
else:
pyname += '_'
return pyname
# Until bug https://bugzilla.gnome.org/show_bug.cgi?id=652127 is fixed, we
# need to reimplement inspect.getmembers. GObject introspection doesn't
# play nice with it.
def getmembers(obj, check):
members = []
for k in dir(obj):
try:
attr = getattr(obj, k)
except:
continue
if check(attr):
members.append((k, attr))
members.sort()
return members
def dict_from_callback_obj(callback_obj):
'''a dictionary interface to callback_obj'''
methods = getmembers(callback_obj, inspect.ismethod)
aliased_methods = [x[1] for x in methods if hasattr(x[1], 'aliases')]
# a method may have several aliases
#~ @alias('on_btn_foo_clicked')
#~ @alias('on_tool_foo_activate')
#~ on_menu_foo_activate():
#~ pass
alias_groups = [(x.aliases, x) for x in aliased_methods]
aliases = []
for item in alias_groups:
for alias in item[0]:
aliases.append((alias, item[1]))
dict_methods = dict(methods)
dict_aliases = dict(aliases)
results = {}
results.update(dict_methods)
results.update(dict_aliases)
return results
def auto_connect_by_name(callback_obj, builder):
'''finds handlers like on_<widget_name>_<signal> and connects them
i.e. find widget,signal pair in builder and call
widget.connect(signal, on_<widget_name>_<signal>)'''
callback_handler_dict = dict_from_callback_obj(callback_obj)
for item in builder.widgets.items():
(widget_name, widget) = item
signal_ids = []
try:
widget_type = type(widget)
while widget_type:
signal_ids.extend(GObject.signal_list_ids(widget_type))
widget_type = GObject.type_parent(widget_type)
except RuntimeError: # pylint wants a specific error
pass
signal_names = [GObject.signal_name(sid) for sid in signal_ids]
# Now, automatically find any the user didn't specify in glade
for sig in signal_names:
# using convention suggested by glade
sig = sig.replace("-", "_")
handler_names = ["on_%s_%s" % (widget_name, sig)]
# Using the convention that the top level window is not
# specified in the handler name. That is use
# on_destroy() instead of on_windowname_destroy()
if widget is callback_obj:
handler_names.append("on_%s" % sig)
do_connect(item, sig, handler_names,
callback_handler_dict, builder.connections)
log_unconnected_functions(callback_handler_dict, builder.connections)
def do_connect(item, signal_name, handler_names,
callback_handler_dict, connections):
'''connect this signal to an unused handler'''
widget_name, widget = item
for handler_name in handler_names:
target = handler_name in callback_handler_dict.keys()
connection = (widget_name, signal_name, handler_name)
duplicate = connection in connections
if target and not duplicate:
widget.connect(signal_name, callback_handler_dict[handler_name])
connections.append(connection)
logger.debug("connect builder by name '%s','%s', '%s'",
widget_name, signal_name, handler_name)
def log_unconnected_functions(callback_handler_dict, connections):
'''log functions like on_* that we could not connect'''
connected_functions = [x[2] for x in connections]
handler_names = callback_handler_dict.keys()
unconnected = [x for x in handler_names if x.startswith('on_')]
for handler_name in connected_functions:
try:
unconnected.remove(handler_name)
except ValueError:
pass
for handler_name in unconnected:
logger.debug("Not connected to builder '%s'", handler_name)

View File

@ -0,0 +1,61 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
from gi.repository import Gtk # pylint: disable=E0611
import logging
logger = logging.getLogger('mugshot_lib')
from . helpers import get_builder, show_uri, get_help_uri
class CameraDialog(Gtk.Dialog):
__gtype_name__ = "CameraDialog"
def __new__(cls):
"""Special static method that's automatically called by Python when
constructing a new instance of this class.
Returns a fully instantiated PreferencesDialog object.
"""
builder = get_builder('CameraMugshotDialog')
new_object = builder.get_object("camera_mugshot_dialog")
new_object.finish_initializing(builder)
return new_object
def finish_initializing(self, builder):
"""Called while initializing this instance in __new__
finish_initalizing should be called after parsing the ui definition
and creating a PreferencesDialog object with it in order to
finish initializing the start of the new PerferencesMugshotDialog
instance.
Put your initialization code in here and leave __init__ undefined.
"""
# Get a reference to the builder and set up the signals.
self.builder = builder
self.ui = builder.get_ui(self, True)
# code for other initialization actions should be added here
def on_btn_close_clicked(self, widget, data=None):
self.destroy()
def on_btn_help_clicked(self, widget, data=None):
show_uri(self, "ghelp:%s" % get_help_uri('preferences'))

View File

@ -0,0 +1,89 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
from gi.repository import Gio, Gtk # pylint: disable=E0611
import logging
logger = logging.getLogger('mugshot_lib')
from . helpers import get_builder, show_uri, get_help_uri
# This class is meant to be subclassed by MugshotWindow. It provides
# common functions and some boilerplate.
class Window(Gtk.Window):
__gtype_name__ = "Window"
# To construct a new instance of this method, the following notable
# methods are called in this order:
# __new__(cls)
# __init__(self)
# finish_initializing(self, builder)
# __init__(self)
#
# For this reason, it's recommended you leave __init__ empty and put
# your initialization code in finish_initializing
def __new__(cls):
"""Special static method that's automatically called by Python when
constructing a new instance of this class.
Returns a fully instantiated BaseMugshotWindow object.
"""
builder = get_builder('MugshotWindow')
new_object = builder.get_object("mugshot_window")
new_object.finish_initializing(builder)
return new_object
def finish_initializing(self, builder):
"""Called while initializing this instance in __new__
finish_initializing should be called after parsing the UI definition
and creating a MugshotWindow object with it in order to finish
initializing the start of the new MugshotWindow instance.
"""
# Get a reference to the builder and set up the signals.
self.builder = builder
self.ui = builder.get_ui(self, True)
self.CameraDialog = None # class
self.camera_dialog = None # instance
self.settings = Gio.Settings("apps.mugshot")
self.settings.connect('changed', self.on_preferences_changed)
def on_help_activate(self, widget, data=None):
show_uri(self, "ghelp:%s" % get_help_uri())
def on_menu_camera_activate(self, widget, data=None):
"""Display the camera window for mugshot."""
if self.camera_dialog is not None:
logger.debug('show existing camera_dialog')
self.camera_dialog.show()
elif self.CameraDialog is not None:
logger.debug('create new camera_dialog')
self.camera_dialog = self.CameraDialog() # pylint: disable=E1102
#self.camera_dialog.connect('destroy', self.on_camera_dialog_destroyed)
self.camera_dialog.connect('apply', self.on_camera_dialog_apply)
self.camera_dialog.show()
def on_destroy(self, widget, data=None):
"""Called when the MugshotWindow is closed."""
# Clean up code for saving application state should be added here.
Gtk.main_quit()
def on_preferences_changed(self, settings, key, data=None):
logger.debug('preference changed: %s = %s' % (key, str(settings.get_value(key))))

View File

@ -0,0 +1,25 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
'''facade - makes mugshot_lib package easy to refactor
while keeping its api constant'''
from . helpers import set_up_logging
from . Window import Window
from . mugshotconfig import get_version

View File

@ -0,0 +1,111 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
"""Helpers for an Ubuntu application."""
import logging
import os
from . mugshotconfig import get_data_file
from . Builder import Builder
from locale import gettext as _
def get_builder(builder_file_name):
"""Return a fully-instantiated Gtk.Builder instance from specified ui
file
:param builder_file_name: The name of the builder file, without extension.
Assumed to be in the 'ui' directory under the data path.
"""
# Look for the ui file that describes the user interface.
ui_filename = get_data_file('ui', '%s.ui' % (builder_file_name,))
if not os.path.exists(ui_filename):
ui_filename = None
builder = Builder()
builder.set_translation_domain('mugshot')
builder.add_from_file(ui_filename)
return builder
# Owais Lone : To get quick access to icons and stuff.
def get_media_file(media_file_name):
media_filename = get_data_file('media', '%s' % (media_file_name,))
if not os.path.exists(media_filename):
media_filename = None
return "file:///"+media_filename
class NullHandler(logging.Handler):
def emit(self, record):
pass
def set_up_logging(opts):
# add a handler to prevent basicConfig
root = logging.getLogger()
null_handler = NullHandler()
root.addHandler(null_handler)
formatter = logging.Formatter("%(levelname)s:%(name)s: %(funcName)s() '%(message)s'")
logger = logging.getLogger('mugshot')
logger_sh = logging.StreamHandler()
logger_sh.setFormatter(formatter)
logger.addHandler(logger_sh)
lib_logger = logging.getLogger('mugshot_lib')
lib_logger_sh = logging.StreamHandler()
lib_logger_sh.setFormatter(formatter)
lib_logger.addHandler(lib_logger_sh)
# Set the logging level to show debug messages.
if opts.verbose:
logger.setLevel(logging.DEBUG)
logger.debug('logging enabled')
if opts.verbose > 1:
lib_logger.setLevel(logging.DEBUG)
def get_help_uri(page=None):
# help_uri from source tree - default language
here = os.path.dirname(__file__)
help_uri = os.path.abspath(os.path.join(here, '..', 'help', 'C'))
if not os.path.exists(help_uri):
# installed so use gnome help tree - user's language
help_uri = 'mugshot'
# unspecified page is the index.page
if page is not None:
help_uri = '%s#%s' % (help_uri, page)
return help_uri
def show_uri(parent, link):
from gi.repository import Gtk # pylint: disable=E0611
screen = parent.get_screen()
Gtk.show_uri(screen, link, Gtk.get_current_event_time())
def alias(alternative_function_name):
'''see http://www.drdobbs.com/web-development/184406073#l9'''
def decorator(function):
'''attach alternative_function_name(s) to function'''
if not hasattr(function, 'aliases'):
function.aliases = []
function.aliases.append(alternative_function_name)
return function
return decorator

View File

@ -0,0 +1,69 @@
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
__all__ = [
'project_path_not_found',
'get_data_file',
'get_data_path',
]
# Where your project will look for your data (for instance, images and ui
# files). By default, this is ../data, relative your trunk layout
__mugshot_data_directory__ = '../data/'
__license__ = 'GPL-3'
__version__ = '0.1'
import os
from locale import gettext as _
class project_path_not_found(Exception):
"""Raised when we can't find the project directory."""
def get_data_file(*path_segments):
"""Get the full path to a data file.
Returns the path to a file underneath the data directory (as defined by
`get_data_path`). Equivalent to os.path.join(get_data_path(),
*path_segments).
"""
return os.path.join(get_data_path(), *path_segments)
def get_data_path():
"""Retrieve mugshot data path
This path is by default <mugshot_lib_path>/../data/ in trunk
and /usr/share/mugshot in an installed version but this path
is specified at installation time.
"""
# Get pathname absolute or relative.
path = os.path.join(
os.path.dirname(__file__), __mugshot_data_directory__)
abs_data_path = os.path.abspath(path)
if not os.path.exists(abs_data_path):
raise project_path_not_found
return abs_data_path
def get_version():
return __version__

47
build/scripts-2.7/mugshot Executable file
View File

@ -0,0 +1,47 @@
#!/usr/bin/python
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2013 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 version 3, as published
# by the Free Software Foundation.
#
# 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/>.
### END LICENSE
### DO NOT EDIT THIS FILE ###
import sys
import os
import locale
locale.textdomain('mugshot')
# Add project root directory (enable symlink and trunk execution)
PROJECT_ROOT_DIRECTORY = os.path.abspath(
os.path.dirname(os.path.dirname(os.path.realpath(sys.argv[0]))))
python_path = []
if os.path.abspath(__file__).startswith('/opt'):
locale.bindtextdomain('mugshot', '/opt/extras.ubuntu.com/mugshot/share/locale')
syspath = sys.path[:] # copy to avoid infinite loop in pending objects
for path in syspath:
opt_path = path.replace('/usr', '/opt/extras.ubuntu.com/mugshot')
python_path.insert(0, opt_path)
sys.path.insert(0, opt_path)
os.putenv("XDG_DATA_DIRS", "%s:%s" % ("/opt/extras.ubuntu.com/mugshot/share/", os.getenv("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/")))
if (os.path.exists(os.path.join(PROJECT_ROOT_DIRECTORY, 'mugshot'))
and PROJECT_ROOT_DIRECTORY not in sys.path):
python_path.insert(0, PROJECT_ROOT_DIRECTORY)
sys.path.insert(0, PROJECT_ROOT_DIRECTORY)
if python_path:
os.putenv('PYTHONPATH', "%s:%s" % (os.getenv('PYTHONPATH', ''), ':'.join(python_path))) # for subprocesses
import mugshot
mugshot.main()

View File

@ -0,0 +1,8 @@
[Desktop Entry]
Name=About Me
Comment=Configure your profile image and contact details
Categories=XFCE;GNOME;GTK;Settings;X-GNOME-Settings-Panel;X-GNOME-PersonalSettings;DesktopSettings;X-XFCE;
Exec=mugshot
Icon=mugshot
Terminal=false
Type=Application

2
debian/copyright vendored
View File

@ -1,6 +1,6 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: mugshot
Upstream-Contact: UNKNOWN <UNKNOWN>
Upstream-Contact: Sean Davis <smd.seandavis@gmail.com>
Source: UNKNOWN
Files: *

View File

@ -143,13 +143,13 @@ class InstallAndUpdateDataDirectory(DistUtilsExtra.auto.install_auto):
DistUtilsExtra.auto.setup(
name='mugshot',
version='0.1',
version='13.07',
license='GPL-3',
#author='Your Name',
#author_email='email@ubuntu.com',
author='Sean Davis',
author_email='smd.seandavis@gmail.com',
#description='UI for managing …',
#long_description='Here a longer description',
#url='https://launchpad.net/mugshot',
url='https://launchpad.net/mugshot',
cmdclass={'install': InstallAndUpdateDataDirectory}
)