diff --git a/.quickly b/.quickly index dbd4520..adb7c31 100644 --- a/.quickly +++ b/.quickly @@ -2,3 +2,5 @@ project = mugshot version = 12.08.1 template = ubuntu-application dependencies = +lp_id = mugshot +ppa = mugshot-ppa diff --git a/build/lib.linux-x86_64-2.7/mugshot/CameraMugshotDialog.py b/build/lib.linux-x86_64-2.7/mugshot/CameraMugshotDialog.py new file mode 100644 index 0000000..c09e4f5 --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot/CameraMugshotDialog.py @@ -0,0 +1,343 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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,)) + } + diff --git a/build/lib.linux-x86_64-2.7/mugshot/MugshotWindow.py b/build/lib.linux-x86_64-2.7/mugshot/MugshotWindow.py new file mode 100644 index 0000000..be794d5 --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot/MugshotWindow.py @@ -0,0 +1,645 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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 '\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('')[1].split('')[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('')[0] + # First Name + if 'name="givenname"' in line: + new = new + '%s\n' % \ + get_entry_value(self.first_name_entry) + # Last Name + elif 'name="sn"' in line: + new = new + '%s\n' % \ + get_entry_value(self.last_name_entry) + # Initials + elif 'name="initials"' in line: + new = new + '%s\n' % \ + get_entry_value(self.initials_entry) + # Email + elif 'name="mail"' in line: + new = new + '%s\n' % \ + get_entry_value(self.email_entry) + # Home Phone + elif 'name="homephone"' in line: + new = new + '%s\n' % \ + get_entry_value(self.home_phone_entry) + # Office Phone + elif 'name="telephonenumber"' in line: + new = new + '%s\n' % \ + get_entry_value(self.office_phone_entry) + # Fax Number + elif 'name="facsimiletelephonenumber"' in line: + new = new + '%s\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) + diff --git a/build/lib.linux-x86_64-2.7/mugshot/__init__.py b/build/lib.linux-x86_64-2.7/mugshot/__init__.py new file mode 100644 index 0000000..92f9969 --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot/__init__.py @@ -0,0 +1,44 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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() diff --git a/build/lib.linux-x86_64-2.7/mugshot_lib/Builder.py b/build/lib.linux-x86_64-2.7/mugshot_lib/Builder.py new file mode 100644 index 0000000..3c3f461 --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot_lib/Builder.py @@ -0,0 +1,325 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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__ and connects them + + i.e. find widget,signal pair in builder and call + widget.connect(signal, on__)''' + + 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) diff --git a/build/lib.linux-x86_64-2.7/mugshot_lib/CameraDialog.py b/build/lib.linux-x86_64-2.7/mugshot_lib/CameraDialog.py new file mode 100644 index 0000000..cf8aae3 --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot_lib/CameraDialog.py @@ -0,0 +1,61 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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')) + diff --git a/build/lib.linux-x86_64-2.7/mugshot_lib/Window.py b/build/lib.linux-x86_64-2.7/mugshot_lib/Window.py new file mode 100644 index 0000000..6674951 --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot_lib/Window.py @@ -0,0 +1,89 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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)))) + diff --git a/build/lib.linux-x86_64-2.7/mugshot_lib/__init__.py b/build/lib.linux-x86_64-2.7/mugshot_lib/__init__.py new file mode 100644 index 0000000..bc3d9e7 --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot_lib/__init__.py @@ -0,0 +1,25 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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 + diff --git a/build/lib.linux-x86_64-2.7/mugshot_lib/helpers.py b/build/lib.linux-x86_64-2.7/mugshot_lib/helpers.py new file mode 100644 index 0000000..15ca93f --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot_lib/helpers.py @@ -0,0 +1,111 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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 diff --git a/build/lib.linux-x86_64-2.7/mugshot_lib/mugshotconfig.py b/build/lib.linux-x86_64-2.7/mugshot_lib/mugshotconfig.py new file mode 100644 index 0000000..7551994 --- /dev/null +++ b/build/lib.linux-x86_64-2.7/mugshot_lib/mugshotconfig.py @@ -0,0 +1,69 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2013 Sean Davis +# 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 . +### 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 /../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__ diff --git a/build/scripts-2.7/mugshot b/build/scripts-2.7/mugshot new file mode 100755 index 0000000..4396fb9 --- /dev/null +++ b/build/scripts-2.7/mugshot @@ -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 +# 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 . +### 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() diff --git a/build/share/applications/mugshot.desktop b/build/share/applications/mugshot.desktop new file mode 100644 index 0000000..d51a0ec --- /dev/null +++ b/build/share/applications/mugshot.desktop @@ -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 diff --git a/debian/copyright b/debian/copyright index a80ca45..f743886 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,6 +1,6 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: mugshot -Upstream-Contact: UNKNOWN +Upstream-Contact: Sean Davis Source: UNKNOWN Files: * diff --git a/setup.py b/setup.py index ba71c1f..bda2759 100644 --- a/setup.py +++ b/setup.py @@ -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} )