pax_global_header00006660000000000000000000000064121720507100014505gustar00rootroot0000000000000052 comment=7341be6d299a4b8b3e83da293c17626ac483fbac python-cream-0.5.3/000077500000000000000000000000001217205071000141205ustar00rootroot00000000000000python-cream-0.5.3/.gitignore000066400000000000000000000000061217205071000161040ustar00rootroot00000000000000*.pyc python-cream-0.5.3/cream/000077500000000000000000000000001217205071000152075ustar00rootroot00000000000000python-cream-0.5.3/cream/__init__.py000066400000000000000000000062121217205071000173210ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import signal from cream.util import cached_property from cream.util import unique from .base import Component from .path import CREAM_DATA_DIRS class Module(Component, unique.UniqueApplication): """ This is the baseclass for every Cream module. It bundles features you would need within your application or service, such as: - an GObject mainloop, - logging capabilities, - and meta data handling. """ def __init__(self, module_id, module_name='', *args, **kwargs): manifest_path = '' for directory in CREAM_DATA_DIRS: for sub_dir in (module_id, module_name): path = os.path.join(directory, sub_dir, 'manifest.xml') if os.path.exists(path): manifest_path = path break Component.__init__(self, manifest_path, *args, **kwargs) unique.UniqueApplication.__init__(self, module_id) def main(self, enable_threads=True): """ Run a GObject-mainloop. :param enable_threads: Whether to enable GObjects threading capabilities. This can have negative impact on some applications. Please use with care! :type enable_threads: `bool` """ signal.signal(signal.SIGTERM, self.signal_cb) from gi.repository import GObject as gobject if enable_threads: gobject.threads_init() self._mainloop = gobject.MainLoop() try: self._mainloop.run() except (SystemError, KeyboardInterrupt): # shut down gracefully. self.quit() @cached_property def messages(self): from cream.log import Messages return Messages(id=self.context.manifest['id']) def signal_cb(self, signal, frame): if signal == signal.SIGTERM: self.quit() def quit(self): """ Quit the mainloop and exit the application. """ self.messages.debug("Shutting down quietly. Protesting wouldn't make sense. I'm just a machine. Grrrrmmm.") unique.UniqueApplication.quit(self) # __finalize__ all registered features: for feature in self._features: feature.__finalize__() self._mainloop.quit() python-cream-0.5.3/cream/base.py000066400000000000000000000107551217205071000165030ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os from cream.util import get_source_file from .manifest import Manifest from .features import FEATURES, NoSuchFeature from .path import CREAM_DATA_HOME, CREAM_DATA_DIR, VIRTUALENV_DATA_HOME class Context(object): def __init__(self, path, user_path_prefix='', use_id_in_path=False): self.path = path self.user_path_prefix = user_path_prefix self.environ = os.environ self.working_directory = os.path.dirname(self.path) self.manifest = Manifest(self.path) self.use_id_in_path = use_id_in_path self.in_virtualenv = 'VIRTUAL_ENV' in os.environ def get_path(self): """ Returns the base path of this context. """ return os.path.dirname(self.path) def get_system_path(self): """ Returns the system wide data path for this context. Files which are stored here are only to be read from. """ if self.use_id_in_path: return os.path.join(CREAM_DATA_DIR, self.manifest['id']) else: return os.path.join(CREAM_DATA_DIR, 'cream-' + self.manifest['name'].lower()) def get_user_path(self): """ Returns the user specific data path for this context. Files which belong to this context can be saved here. """ if self.use_id_in_path: dirname = self.manifest['id'] else: dirname = 'cream-' + self.manifest['name'].lower() user_path = os.path.join( CREAM_DATA_HOME, self.user_path_prefix, dirname ) if not os.path.exists(user_path): os.makedirs(user_path) return user_path def get_virtualenv_path(self): """ Returns the data directory in the virtual env if present. """ if not self.in_virtualenv: return '' if self.use_id_in_path: dirname = self.manifest['id'] else: dirname = 'cream-' + self.manifest['name'].lower() return os.path.join( VIRTUALENV_DATA_HOME, self.user_path_prefix, dirname ) class Component(object): """ Baseclass for e. g. cream.Module and cream.extensions.Extension. """ __manifest__ = 'manifest.xml' def __init__(self, path=None, user_path_prefix='', use_id_in_path=False): if path: self.__manifest__ = path else: sourcefile = os.path.abspath(get_source_file(self.__class__)) base_path = os.path.dirname(sourcefile) self.__manifest__ = os.path.join(base_path, self.__manifest__) # Create context and load manifest file... self.context = Context(self.__manifest__, user_path_prefix, use_id_in_path) try: os.chdir(self.context.working_directory) except OSError: import warnings warnings.warn("Could not change directory to the module's working directory!") # Load required features... self._features = list() self._loaded_features = set() for feature_name, kwargs in self.context.manifest['features']: try: feature_class = FEATURES[feature_name] except KeyError: raise NoSuchFeature("Could not load feature '%s'" % feature_name) else: self.load_feature(feature_class, **kwargs) def load_feature(self, feature_class, **kwargs): """ Make sure a feature is only loaded once for a Component. """ if feature_class not in self._loaded_features: self._features.append(feature_class(self, **kwargs)) self._loaded_features.add(feature_class) python-cream-0.5.3/cream/config/000077500000000000000000000000001217205071000164545ustar00rootroot00000000000000python-cream-0.5.3/cream/config/__init__.py000066400000000000000000000243361217205071000205750ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # TODO: Rewrite this. from gi.repository import Gtk as gtk from gpyconf import Configuration as _Configuration from gpyconf.fields import Field from .backend import CreamXMLBackend from cream.util import flatten, cached_property PROFILE_EXISTS_MARKUP = ''' \ Sorry! A profile with the name {0} already exists! Please choose a different name!''' class MissingConfigurationDefinitionFile(Exception): """ Raised if one tries to access a module's configuration but that module hasn't defined any. """ pass class ProfileNotEditable(Exception): pass class ConfigurationProfile(object): """ A configuration profile. Holds name and assigned values. """ is_editable = True def __init__(self, name, values, editable=True): self.name = name self.default_values = values self.is_editable = editable self._values = values @classmethod def fromdict(cls, dct, default_profile): values = default_profile.values.copy() values.update(dct.get('values', ())) return cls(dct.pop('name'), values, dct.pop('editable', True)) @property def values(self): return self._values # TODO: Very repetitive @values.setter def values(self, value): if not self.is_editable: raise ProfileNotEditable(self) else: self._values = value def update(self, iterable): if not self.is_editable: raise ProfileNotEditable(self) self.values.update(iterable) def set(self, name, value): if not self.is_editable: raise ProfileNotEditable(self) self.values[name] = value def __repr__(self): return "" % (self.name, not self.is_editable and ' (not editable)' or '') class DefaultProfile(ConfigurationProfile): """ Default configuration profile (using in-code defined values) """ def __init__(self, values): ConfigurationProfile.__init__(self, 'Default Profile', values, editable=False) class ProfileExistsError(Exception): def __init__(self, name): Exception.__init__(self, "A profile named '%s' already exists" % name) class ProfileList(list): """ List of profiles """ default = None active = None active_index = 0 def __init__(self, default_profile): list.__init__(self) list.append(self, default_profile) self.default = default_profile def insert(self, index, profile, overwrite=False): assert index if not isinstance(profile, ConfigurationProfile): profile = ConfigurationProfile.fromdict(profile, self.default) _, old_profile = self.find_by_name(profile.name) if old_profile is not None: if not overwrite: raise ProfileExistsError(profile) else: old_profile.values = profile.values else: list.insert(self, index, profile) def append(self, *args, **kwargs): self.insert(len(self), *args, **kwargs) add = append def find_by_name(self, name): """ Returns a `(index, profile)` tuple being the `Profile` instance holding `name` as `name` attribute and its index in this list. If no such profile exists, returns a `(None, None)` tuple instead. """ for index, profile in enumerate(self): if profile.name == name: return index, profile return None, None def _use(self, profile): if isinstance(profile, int): try: self.active = self[profile] self.active_index = profile except IndexError: self._use(0) else: self.active = profile self.active_index = self.index(profile) class Configuration(_Configuration): """ Base class for all cream configurations. """ backend = CreamXMLBackend profiles = () @cached_property def frontend(self): from .frontend import CreamFrontend return CreamFrontend def __init__(self, scheme_path, path, **kwargs): # Make sure this instance's `fields` dict is *not* the classes' # `fields` dict (hence, the `fields` attribute of class `cls`), # but a copy of it. # TODO: There has to be a better way. self.fields = self.fields.copy() backend = CreamXMLBackend(scheme_path, path) try: configuration_scheme = backend.read_scheme() for name, field in configuration_scheme.iteritems(): self._add_field(name, field) except MissingConfigurationDefinitionFile: pass _Configuration.__init__(self, backend_instance=backend, **kwargs) def _add_field(self, name, field): field.field_var = name self.fields[name] = field field.connect('value-changed', self.on_field_value_changed) def read(self): if not self.initially_read: predefined_profiles = self.profiles else: predefined_profiles = () self.profiles = ProfileList(DefaultProfile(self.fields.name_value_dict)) # TODO: remove static options static_options, profiles = self.backend_instance.read() for field_name, value in static_options.iteritems(): setattr(self, field_name, value) active_profile = 0 for profile in flatten((profiles, predefined_profiles)): position = profile.pop('position') self.profiles.insert(position, profile, overwrite=True) if profile.pop('selected', False): active_profile = position self.use_profile(active_profile) self.initially_read = True def __setattr__(self, attr, value): new_value = super(Configuration, self).__setattr__(attr, value) if new_value is not None and not self.fields[attr].static: self.profiles.active.set(attr, new_value) def __getattr__(self, name): field = self.fields.get(name) if field is not None: if field.static: return field.value else: return self.profiles.active.values[name] else: raise AttributeError("No such attribute '%s'" % name) def use_profile(self, profile): self.profiles._use(profile) for name, instance in self.fields.iteritems(): if instance.static: continue instance.value = self.profiles.active.values[name] self.profiles.active.values[name] = instance.value # FRONTEND: def _init_frontend(self, fields): _Configuration._init_frontend(self, fields) self.window = self.frontend_instance self.window.add_profiles(self.profiles) self.window.connect('profile-changed', self.frontend_profile_changed) self.window.connect('add-profile', self.frontend_add_profile) self.window.connect('remove-profile', self.frontend_remove_profile) self.window.set_active_profile_index(self.profiles.active_index) def frontend_field_value_changed(self, *args): if not self._ignore_frontend: _Configuration.frontend_field_value_changed(self, *args) def frontend_profile_changed(self, sender, profile_name, index): """ Profile selection was changed by the frontend (user) """ self._ignore_frontend = True self.use_profile(index) self._ignore_frontend = False self.window.editable = self.profiles.active.is_editable self.save() def frontend_add_profile(self, sender, profile_name, position): """ User added a profile using the "add profile" button """ profile = ConfigurationProfile(profile_name, self.fields.name_value_dict) try: self.profiles.insert(position, profile) except ProfileExistsError: dialog = gtk.MessageDialog( parent=None, flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_ERROR, buttons=gtk.BUTTONS_CLOSE ) dialog.set_markup(PROFILE_EXISTS_MARKUP.format(profile.name)) dialog.run() dialog.destroy() else: self.window.insert_profile(profile, position) self.window.set_active_profile_index(position) self.save() def frontend_remove_profile(self, sender, position): """ User removed a profile using the "remove profile" button """ del self.profiles[position] self.window.remove_profile(position) self.save() def run_frontend(self): _Configuration.run_frontend(self) del self.frontend_instance def show_dialog(self): self.run_frontend() # BACKEND def save(self): self.emit('pre-save') self.backend_instance.save(self.profiles, self.fields) # Patch the `Field` class to make it accept the # cream-specific `static` keyword: def inject_method(klass, method_name): def wrapper(func): original_meth = getattr(klass, method_name) def chained_method(*args, **kwargs): original_meth(*args, **kwargs) func(*args, **kwargs) setattr(klass, method_name, chained_method) return func return wrapper @inject_method(Field, '_external_on_initialized') def on_initialized(self, kwargs): self.static = kwargs.pop('static', False) python-cream-0.5.3/cream/config/backend.py000066400000000000000000000133571217205071000204260ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os from lxml.etree import XMLSyntaxError, parse as parse_xml from cream.util.string import slugify from cream.util.xmlserialize import unserialize_file, unserialize_atomic, serialize_to_file from gpyconf.backends import Backend import gpyconf.fields import gpyconf.contrib.gtk import cream.config.fields from cream.util.dicts import ordereddict FIELD_TYPE_MAP = { 'char' : 'str', 'color' : 'str', 'font' : 'str', 'file' : 'tuple', 'integer' : 'int', 'hotkey' : 'str', 'boolean' : 'bool', 'multioption' : 'tuple' } CONFIGURATION_DIRECTORY = 'configuration' STATIC_OPTIONS_FILE = 'static-options.xml' CONFIGURATION_SCHEME_FILE = 'scheme.xml' PROFILE_ROOT_NODE = 'configuration_profile' STATIC_OPTIONS_ROOT_NODE = 'static_options' PROFILE_DIR = 'profiles' def get_field(name): if not name.endswith('Field'): name = name.title() + 'Field' try: return getattr(cream.config.fields, name) except AttributeError: pass try: return getattr(gpyconf.fields, name) except AttributeError: pass try: return getattr(gpyconf.contrib.gtk, name) except AttributeError: raise FieldNotFound(name) class FieldNotFound(Exception): pass class CreamXMLBackend(dict, Backend): compatibility_mode = False def __init__(self, scheme_path, path): Backend.__init__(self, None) dict.__init__(self) self.scheme_path = scheme_path self.path = path self.profile_dir = os.path.join(self.path, PROFILE_DIR) def read_scheme(self): if not os.path.isfile(self.scheme_path): from . import MissingConfigurationDefinitionFile raise MissingConfigurationDefinitionFile("Could not find %r." % self.scheme_path) tree = parse_xml(self.scheme_path) root = tree.getroot() scheme = ordereddict() for child in root.getchildren(): option_name = child.tag attributes = dict(child.attrib) option_type = attributes.pop('type') if option_type.startswith('multioption'): # TODO: Hrm attributes['default'] = child.attrib.pop('default', None) attributes['options'] = unserialize_atomic(child, FIELD_TYPE_MAP) else: if not ( FIELD_TYPE_MAP.get(option_type) in ('list', 'tuple', 'dict') and not child.getchildren() ): attributes['default'] = unserialize_atomic(child, FIELD_TYPE_MAP) scheme[option_name] = get_field(option_type)(**attributes) return scheme def read(self): static_options = {} profiles = [] try: obj = unserialize_file(os.path.join(self.path, 'static-options.xml')) static_options.update(obj) except: pass if not os.path.exists(self.profile_dir): return dict(), tuple() for profile in os.listdir(self.profile_dir): if os.path.isdir(os.path.join(self.profile_dir, profile)): continue try: obj = unserialize_file(os.path.join(self.profile_dir, profile)) except XMLSyntaxError, err: self.warn("Could not parse XML configuration file '{file}': {error}".format( file=profile, error=err)) else: profiles.append(obj) return static_options, profiles def save(self, profile_list, fields): if not os.path.exists(self.profile_dir): os.makedirs(self.profile_dir) # get all saved profiles saved_profiles = {} for profile in os.listdir(self.profile_dir): name = os.path.splitext(profile)[0] saved_profiles[name] = os.path.join(self.profile_dir, profile) for index, profile in enumerate(profile_list): if not profile.is_editable: continue filename = os.path.join(os.path.join(self.path, PROFILE_DIR), slugify(profile.name)+'.xml') serialize_to_file({ 'name' : profile.name, 'values' : profile.values, 'position' : index, 'selected' : profile_list.active == profile }, filename, tag=PROFILE_ROOT_NODE) if profile.name.lower() in saved_profiles: del saved_profiles[profile.name.lower()] # `saved_profiles` now contains profiles, which have been removed # but are still present in the filesystem. Remove them. for profile in saved_profiles.values(): os.remove(profile) static_options = dict((name, field.value) for name, field in fields.iteritems() if field.static) if static_options: serialize_to_file(static_options, os.path.join(self.path, STATIC_OPTIONS_FILE), tag=STATIC_OPTIONS_ROOT_NODE) python-cream-0.5.3/cream/config/fields.py000066400000000000000000000032611217205071000202760ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from gpyconf.fields import FontField as _FontField, MultiOptionField from gpyconf.frontends.gtk import font_description_to_dict, dict_to_font_description MultioptionField = MultiOptionField class FontDict(dict): def to_string(self): return dict_to_font_description(self) @classmethod def fromstring(cls, string): d = font_description_to_dict(string) d['color'] = '#000000' return cls(d) class FontField(_FontField): def custom_default(self): return FontDict(_FontField.custom_default(self)) def to_python(self, value): if isinstance(value, basestring): return FontDict.fromstring(value) else: return FontDict(_FontField.to_python(self, value)) def conf_to_python(self, value): return FontDict(_FontField.conf_to_python(self, value))python-cream-0.5.3/cream/config/frontend.py000066400000000000000000000152651217205071000206560ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from gi.repository import Gtk as gtk from gpyconf.frontends.gtk import ConfigurationDialog from cream.util import joindir MODE_NORMAL = 1 MODE_EDIT = 2 class CreamFrontend(ConfigurationDialog): _editable = True _new_events = ('profile-changed', 'add-profile', 'remove-profile') def __init__(self, *args, **kwargs): self.profiles = [] self._mode = MODE_NORMAL ConfigurationDialog.__init__(self, title='Configuration', *args, **kwargs) self.add_events(self._new_events) self.interface = gtk.Builder() self.interface.add_from_file(joindir(__file__, 'interface/profiles.ui')) self.profile_box_edit = self.interface.get_object('profile_box_edit') self.profile_entry = self.interface.get_object('profile_entry') self.profile_save = self.interface.get_object('profile_save') self.profile_cancel = self.interface.get_object('profile_cancel') self.profile_box_normal = self.interface.get_object('profile_box_normal') self.profile_selector = self.interface.get_object('profile_selector') self.profile_add = self.interface.get_object('profile_add') self.profile_remove = self.interface.get_object('profile_remove') self.profiles_storage = self.interface.get_object('profiles_storage') self.profile_selector.connect('changed', self.on_profile_changed) self.profile_entry.connect('activate', self.change_mode) self.profile_entry.connect('activate', self.on_new_profile_added) self.profile_add.connect('clicked', self.change_mode) self.profile_save.connect('clicked', self.change_mode) self.profile_save.connect('clicked', self.on_new_profile_added) self.profile_cancel.connect('clicked', self.change_mode) self.profile_remove.connect('clicked', self.on_remove_profile) self.alignment = gtk.Alignment.new(1, 0.5, 1, 1) self.alignment.add(self.profile_box_normal) self.layout.pack_start(self.alignment, False, False, 0) self.layout.reorder_child(self.alignment, 0) def add_profiles(self, profiles): """ Add a list or tuple of `Profile`s to the profile selector """ for profile in profiles: self.add_profile(profile) def add_profile(self, profile): """ Add a `Profile` instance to the profile selector """ self.profiles_storage.append([profile.name]) self.profiles.append(profile.name) def insert_profile(self, profile, position): """ Insert `profile` at `position` into the profile selector """ self.profiles_storage.insert(position, [profile.name]) self.profiles.insert(position, profile.name) def remove_profile(self, position): """ Remove entry at `position` from the profile selector """ iter = self.profiles_storage.get_iter_from_string(str(position)) # huaa? self.profiles_storage.remove(iter) self.profile_selector.set_active(position-1) # CALLBACKS def on_profile_changed(self, widget): """ User changed the profile-selector dropdown """ index = widget.get_active() if index < 0: # empty profile selector (should only happen at startup) return self.emit('profile-changed', self.profiles_storage.get_value(widget.get_active_iter(), 0), index ) def on_remove_profile(self, sender): """ User clicked the "remove profile" button """ dialog = gtk.MessageDialog( parent=None, flags=gtk.DialogFlags.MODAL, type=gtk.MessageType.QUESTION, buttons=gtk.ButtonsType.YES_NO) dialog.set_markup("Are you sure that you want to remove profile {0}?\n\nYou will lose all data connected to this profile and won't be able to restore a previously removed profile!".format(self.profiles[self.profile_selector.get_active()])) res = dialog.run() dialog.destroy() if res == gtk.ResponseType.YES: self.emit('remove-profile', self.profile_selector.get_active()) def on_new_profile_added(self, sender): """ User is done with editing the Entry """ name = self.profile_entry.get_text() index = self.profile_selector.get_active() + 1 if name: self.emit('add-profile', name, index) def change_mode(self, sender): """ User clicked on add or save button. Change the mode. """ max_height = max(self.profile_entry.get_allocated_height(), self.profile_selector.get_allocated_height() ) self.alignment.set_size_request(-1, max_height) box = [widget for widget in self.alignment][0] if self._mode == MODE_NORMAL: self.profile_entry.set_text('') self.alignment.remove(box) self.alignment.add(self.profile_box_edit) self.profile_entry.grab_focus() self._mode = MODE_EDIT else: self.alignment.remove(box) self.alignment.add(self.profile_box_normal) self._mode = MODE_NORMAL @property def editable(self): """ `True` if the window is 'editable' (an editable profile is selected) """ return self._editable @editable.setter def editable(self, value): # set widgets sensitive (or not) if value: if not self.editable: self.content.set_sensitive(True) self.profile_remove.set_sensitive(True) else: self.content.set_sensitive(False) self.profile_remove.set_sensitive(False) self._editable = value def set_active_profile_index(self, index): self.profile_selector.set_active(index) def run(self): ConfigurationDialog.run(self) self.dialog.destroy() python-cream-0.5.3/cream/config/interface/000077500000000000000000000000001217205071000204145ustar00rootroot00000000000000python-cream-0.5.3/cream/config/interface/profiles.ui000066400000000000000000000072111217205071000225770ustar00rootroot00000000000000 True True True 0 True True True True gtk-ok False 1 True True True True gtk-cancel False 2 True True profiles_storage 0 0 True True True True gtk-add False 1 True True True True gtk-remove False 2 python-cream-0.5.3/cream/extensions.py000066400000000000000000000050211217205071000177560ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import sys import inspect import imp from . import Component from .manifest import ManifestDB EXTENSIONS = {} def register(ext): # TODO: Error management! path = os.path.abspath(inspect.getsourcefile(ext)) EXTENSIONS[path] = ext return ext class Extension(Component): """ Class for building extensions. """ def __init__(self, interface): Component.__init__(self) self.interface = interface class ExtensionManager(object): """ Class for managing extensions. """ def __init__(self, paths, interface=None): self.paths = paths self.interface = interface self.extensions = ManifestDB(self.paths, type='org.cream.Extension') def load_all(self, interface=None, *args, **kwargs): return map(lambda ext: self._load(ext, interface), self.extensions.by_name.itervalues()) def load_by_name(self, name, interface=None, *args, **kwargs): ext = self.extensions.get(name=name).next() return self._load(ext, interface) def load_by_hash(self, hash, interface=None, *args, **kwargs): ext = self.extensions.get_by_hash(hash) return self._load(ext, interface) def _load(self, extension, interface=None, *args, **kwargs): module_path = extension['entry'].replace('./', '') sys.path.append(module_path) imp.load_module( extension['name'], open(module_path), module_path, ('.py', 'r', imp.PY_SOURCE) ) extension = EXTENSIONS[module_path] instance = extension(interface or self.interface, *args, **kwargs) sys.path.remove(module_path) return instance python-cream-0.5.3/cream/features.py000066400000000000000000000107401217205071000174010ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os from gi.repository import GObject as gobject import weakref FEATURES = dict() class NoSuchFeature(Exception): pass class Feature(object): """ "Feature" that can be "mixed" into Cream components. """ dependencies = None def __new__(cls, component, *args, **kwargs): """ Make sure all dependencies for this feature are loaded. """ if cls.dependencies: for dependency in cls.dependencies: component.load_feature(dependency, *args, **kwargs) return super(Feature, cls).__new__(cls) def __finalize__(self): pass class ConfigurationFeature(Feature): autosave = True def __init__(self, component, read=True): self.component_ref = weakref.ref(component) if read == True or read == 'true': read = True else: read = False Feature.__init__(self) from .config import Configuration scheme_path = os.path.join(component.context.get_path(), 'configuration/scheme.xml') config_dir = os.path.join(component.context.get_user_path(), 'configuration/') component.config = Configuration(scheme_path, config_dir, read=read) self.config = component.config def __finalize__(self): component = self.component_ref() if self.autosave: component.messages.debug("Automatically saving configuration...") self.config.save() class HotkeyFeature(Feature, gobject.GObject): dependencies = (ConfigurationFeature,) __gtype_name__ = 'HotkeyFeature' __gsignals__ = { 'hotkey-activated': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_STRING,)), } def __init__(self, component): from gpyconf.contrib.gtk import HotkeyField import cream.ipc from dbus.exceptions import DBusException Feature.__init__(self) gobject.GObject.__init__(self) self.component = weakref.ref(component) self.component().hotkeys = self try: self.manager = cream.ipc.get_object('org.cream.HotkeyManager', '/org/cream/HotkeyManager') except DBusException: import warnings warnings.warn("Could not connect to the cream hotkey manager") return self.broker = cream.ipc.get_object('org.cream.HotkeyManager', self.manager.register(), interface='org.cream.HotkeyManager.broker') self.broker.connect_to_signal('hotkey_activated', self.hotkey_activated_cb) for name, field in self.component().config.fields.iteritems(): if isinstance(field, HotkeyField): self.broker.set_hotkey(field.action, field.value) field.connect('value-changed', self.configuration_field_value_changed_cb) def configuration_field_value_changed_cb(self, source, field, value): self.broker.set_hotkey(field.action, field.value) def hotkey_activated_cb(self, action): self.emit('hotkey-activated', action) class ExtensionFeature(Feature): def __init__(self, component, directory='extensions'): Feature.__init__(self, component) from cream.extensions import ExtensionManager component.extension_manager = ExtensionManager( [os.path.join(component.context.get_path(), directory), os.path.join(component.context.get_user_path(), 'extensions')], component.extension_interface ) FEATURES.update({ 'org.cream.extensions' : ExtensionFeature, 'org.cream.config' : ConfigurationFeature, 'org.cream.hotkeys' : HotkeyFeature }) python-cream-0.5.3/cream/gui/000077500000000000000000000000001217205071000157735ustar00rootroot00000000000000python-cream-0.5.3/cream/gui/__init__.py000066400000000000000000000070761217205071000201160ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from gi.repository import GObject as gobject, Gtk as gtk import cairo import time import math CURVE_LINEAR = lambda x: x CURVE_SINE = lambda x: math.sin(math.pi / 2 * x) FRAMERATE = 30.0 class CompositeBin(gtk.Fixed): """ A subclass of `GtkFixed` enabling composition of child widgets to the parent widget. """ def __init__(self): self.alpha = 1 self.children = [] gtk.Fixed.__init__(self) self.connect('realize', self.realize_cb) def realize_cb(self, widget): self.get_parent().connect_after('draw', self.draw_cb) def draw_cb(self, widget, ctx): ctx.set_operator(cairo.OPERATOR_OVER) #ctx.rectangle(*event.area) ctx.clip() for child in self.children: alloc = child.get_allocation() ctx.move_to(alloc.x, alloc.y) #ctx.set_source_pixmap(child.window, alloc.x, alloc.y) ctx.paint() return False def add(self, child, x, y): """ Add a widget. :param child: A `GtkWidget` to add to the `CompositedBin`. """ self.children.append(child) child.connect('realize', self.child_realize_cb) self.put(child, x, y) def remove(self, child): gtk.Fixed.remove(self, child) self.children.remove(child) def child_realize_cb(self, widget): try: widget.window.set_composited(True) except: pass def raise_child(self, child): child.window.raise_() self.children.remove(child) self.children.insert(len(self.children), child) self.window.invalidate_rect(child.allocation, True) class Timeline(gobject.GObject): __gtype_name__ = 'Timeline' __gsignals__ = { 'update': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_FLOAT,)), 'completed': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) } def __init__(self, duration, curve): gobject.GObject.__init__(self) self.duration = duration self.curve = curve self._states = [] self._stopped = False def run(self): n_frames = (self.duration / 1000.0) * FRAMERATE while len(self._states) <= n_frames: self._states.append(self.curve(len(self._states) * (1.0 / n_frames))) self._states.reverse() gobject.timeout_add(int(self.duration / n_frames), self.update) def stop(self): self._stopped = True def update(self): if self._stopped: self.emit('completed') return False self.emit('update', self._states.pop()) if len(self._states) == 0: self.emit('completed') return False return True python-cream-0.5.3/cream/gui/builder.py000066400000000000000000000034611217205071000177770ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from gi.repository import Gtk as gtk class GtkBuilderInterface(object): """ Integrates GTKBuilder interfaces into a class so that you can access widgets defined in the UI file as if they were attributes of the class. Example:: class MyGreatWindow(GtkBuilderInterface): def __init__(self): GtkBuilderInterface.__init__(self, '/path/to/ui.glade') self.window.show_all() where ``window`` is a window defined in the UI file. :param builder_file: Path to the UI file """ def __init__(self, builder_file): self._builder_file = builder_file self._builder_tree = gtk.Builder() self._builder_tree.add_from_file(builder_file) def __getattr__(self, attr): obj = self._builder_tree.get_object(attr) if obj is None: raise AttributeError(attr) return obj def connect_signals(self, *a): self._builder_tree.connect_signals(*a) python-cream-0.5.3/cream/gui/svg.py000066400000000000000000000073641217205071000171560ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from xml.dom import minidom import ctypes from ctypes import * _lib = CDLL('librsvg-2.so') gobject = CDLL('libgobject-2.0.so') gobject.g_type_init() class RsvgDimensionData(Structure): _fields_ = [("width", c_int), ("height", c_int), ("em",c_double), ("ex",c_double)] class RsvgPositionData(Structure): _fields_ = [("x", c_int), ("y", c_int)] class PycairoContext(Structure): _fields_ = [("PyObject_HEAD", c_byte * object.__basicsize__), ("ctx", c_void_p), ("base", c_void_p)] handle_get_dimensions = _lib.rsvg_handle_get_dimensions handle_render_cairo = _lib.rsvg_handle_render_cairo handle_render_cairo_sub = _lib.rsvg_handle_render_cairo_sub handle_free = _lib.rsvg_handle_free handle_get_dimensions_sub = _lib.rsvg_handle_get_dimensions_sub handle_get_position_sub = _lib.rsvg_handle_get_position_sub __all__ = [ 'handle_new_from_file', 'handle_new_from_data', 'handle_get_dimensions', 'handle_render_cairo', 'handle_render_cairo_sub', 'RsvgDimensionData', ] def set_id_attribute(element): try: element.setIdAttribute('id') except: pass for c in element.childNodes: set_id_attribute(c) class Handle: def __init__(self, path=None): self.path = path if self.path: self.dom = minidom.parse(self.path) set_id_attribute(self.dom) self.dom.save = self.save_dom xml = self.dom.toxml('utf-8') self.handle = _lib.rsvg_handle_new_from_data(xml, len(xml)) def save_dom(self): handle_free(self.handle) xml = self.dom.toxml('utf-8') self.handle = _lib.rsvg_handle_new_from_data(xml, len(xml)) @classmethod def new_from_data(cls, data): self = Handle() self.dom = minidom.parseString(data) set_id_attribute(self.dom) self.dom.save = self.save_dom xml = self.dom.toxml('utf-8') self.handle = _lib.rsvg_handle_new_from_data(xml, len(xml)) return self def get_dimensions_sub(self, element): dim = RsvgDimensionData() handle_get_dimensions_sub(self.handle, ctypes.byref(dim), element) return dim.width, dim.height def get_position_sub(self, element): dim = RsvgPositionData() handle_get_position_sub(self.handle, ctypes.byref(dim), element) return dim.x, dim.y def render_cairo(self, ctx): handle_render_cairo(self.handle, PycairoContext.from_address(id(ctx)).ctx) def render_cairo_sub(self, ctx, element): handle_render_cairo_sub(self.handle, PycairoContext.from_address(id(ctx)).ctx, element) def get_dimension_data(self): dim = RsvgDimensionData() handle_get_dimensions(self.handle, ctypes.byref(dim)) return dim.width, dim.height def __del__(self): handle_free(self.handle) python-cream-0.5.3/cream/gui/widgets/000077500000000000000000000000001217205071000174415ustar00rootroot00000000000000python-cream-0.5.3/cream/gui/widgets/__init__.py000066400000000000000000000000001217205071000215400ustar00rootroot00000000000000python-cream-0.5.3/cream/gui/widgets/slider.py000066400000000000000000000077331217205071000213070ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from gi.repository import GObject as gobject, Gtk as gtk class Slider(gtk.Viewport): active_widget = None _size_cache = None def __init__(self): gtk.Viewport.__init__(self) self.timeouts = dict() self.set_shadow_type(gtk.SHADOW_NONE) self.layout = gtk.HBox(True) self.content = gtk.EventBox() self.content.add(self.layout) self.container = gtk.Fixed() self.container.add(self.content) self.add(self.container) self.connect('size-allocate', self.size_allocate_cb) def slide_to(self, widget): self.active_widget = widget def update(source, status): pos = end_position - start_position adjustment.set_value(start_position + int(round(status * pos))) adjustment = self.get_hadjustment() start_position = adjustment.get_value() end_position = widget.get_allocation().x if start_position != end_position: timeline = cream.gui.Timeline(500, cream.gui.CURVE_SINE) timeline.connect('update', update) timeline.run() def size_allocate_cb(self, source, allocation): if self._size_cache != allocation and self.active_widget: adjustment = self.get_hadjustment() adjustment.set_value(self.active_widget.get_allocation().x) self._size_cache = allocation width = (len(self.layout.get_children()) or 1) * allocation.width self.content.set_size_request(width, allocation.height) def append_widget(self, widget): self.layout.pack_start(widget, True, True, 0) def add_slide_timeout(self, widget, seconds): """ Adds a timeout for ``widget`` to slide in after ``seconds``. """ if widget in self.timeouts: raise RuntimeError("A timeout for '%s' was already added" % widget) callback = lambda: self.slide_to(widget) self.timeouts[widget] = (gobject.timeout_add_seconds(seconds, callback), seconds) def remove_slide_timeout(self, widget): """ Removes a timeout previously added by ``add_slide_timeout``. """ try: gobject.source_remove(self.timeouts.pop(widget)[0]) except KeyError: pass def reset_slide_timeout(self, widget, seconds=None): """ Shorthand to ``remove_slide_timeout`` plus ``add_slide_timeout``. """ if seconds is None: try: seconds = self.timeouts[widget][1] except KeyError: pass else: self.remove_slide_timeout(widget) self.add_slide_timeout(widget, seconds) def try_remove_slide_timeout(self, widget): try: self.remove_slide_timeout(widget) except RuntimeError: pass def try_reset_slide_timeout(self, widget, *args, **kwargs): """ Like ``reset_slide_timeout``, but fails silently if the timeout for``widget`` does not exist. """ if widget in self.timeouts: self.reset_slide_timeout(widget, *args, **kwargs) python-cream-0.5.3/cream/ipc/000077500000000000000000000000001217205071000157625ustar00rootroot00000000000000python-cream-0.5.3/cream/ipc/__init__.py000066400000000000000000000162461217205071000201040ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from types import FunctionType import gi from gi.repository import GObject as gobject import dbus import dbus.proxies import dbus.service from dbus.lowlevel import SignalMessage import dbus.mainloop.glib dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) from . import hacks, properties from .tools import bus_name_to_path, path_to_bus_name class IpcProxyInterface(dbus.Interface): def get_extension(self, name, interface=None): return get_object(self.bus_name, '/'.join((self.object_path, name)), interface) SESSION_BUS = dbus.SessionBus() SYSTEM_BUS = dbus.SystemBus() PATH_SEP = '/' _INTERFACE_SENTINEL = 'THIS.IS.AN.INTERFACE.NAME.USED.AS.A.SENTINEL.AND.I.LIKE.TURTLES' def get_object(modname, path=None, interface=None, bus=SESSION_BUS): if path is None: path = bus_name_to_path(modname) if interface is None: interface = path_to_bus_name(path) return IpcProxyInterface( bus.get_object(modname, path), interface ) class IpcError(Exception): pass if gi.version_info[0] == 3 and gi.version_info[1] >= 8: GObjectMeta = gobject.GObject.__class__ else: GObjectMeta = gobject.GObjectMeta class ObjectMeta(dbus.service.InterfaceType, GObjectMeta): def __new__(mcs, name, bases, dct): dct['_dbus_interface'] = _INTERFACE_SENTINEL cls = dbus.service.InterfaceType.__new__(mcs, name, bases, dct) # begin to add all ipc methods already defined. cls._ipc_methods = {} # TODO: we don't need that, cause noone should derive # from a Module subclass, do we? # first, get the ipc methods of the base classes # now, add all new methods. for name, member in dct.iteritems(): if (isinstance(member, FunctionType) and getattr(member, '_ipc_expose', False)): cls._expose_method(member, member._ipc_in_signature, member._ipc_out_signature, member._ipc_interface ) # add all signals if '__ipc_signals__' in dct: for name, signature in dct['__ipc_signals__'].iteritems(): if not isinstance(signature, basestring): signature, interface = signature else: interface = None cls._expose_signal(name, signature, interface) return cls class Object(dbus.service.Object, gobject.GObject): __gtype_name__ = 'Object' __metaclass__ = ObjectMeta @classmethod def _expose_method(cls, func, in_signature, out_signature, interface=None): """ add a method to the dbus class. That can also be done after the class initialization. """ if interface is None: interface = cls._dbus_interface hacks.add_method( cls, func, in_signature=in_signature, out_signature=out_signature, dbus_interface=interface, ) @classmethod def _expose_signal(cls, name, signature, interface=None): if interface is None: interface = cls._dbus_interface hacks.add_signal( cls, name, signature, interface=interface, ) @classmethod def _set_interface(cls, new_interface): clsname = cls.__module__ + '.' + cls.__name__ if clsname in cls._dbus_class_table: entry = cls._dbus_class_table[clsname] if cls._dbus_interface in entry: entry[new_interface] = entry.pop(cls._dbus_interface) for func in entry[new_interface].itervalues(): func._dbus_interface = new_interface cls._dbus_interface = new_interface def emit_signal(self, name, *args): """ emit the dbus signal called *name* with arguments. """ if name not in self.__ipc_signals__: raise IpcError("Unknown signal: '%s'" % name) # Okay. Let's emit it. for location in self.locations: signature = self.__ipc_signals__[name] if not isinstance(signature, basestring): signature, interface = signature else: interface = self._interface message = SignalMessage( self._path, interface, name) message.append(signature=signature, *args) location[0].send_message(message) def __init__(self, bus_name, path, interface=None, bus=SESSION_BUS): self._dbus_bus_name = dbus.service.BusName(bus_name, bus) dbus.service.Object.__init__(self, self._dbus_bus_name, path) gobject.GObject.__init__(self) self._bus = bus self._bus_name = bus_name self._path = path self._interface = interface or path_to_bus_name(path) # set the new interface self.__class__._set_interface(self._interface) """ class Module(Object): def __init__(self, bus_name): Object.__init__(self, BUS, bus_name, bus_name_to_path(bus_name) ) self._bus_name = bus_name self._dbus_bus_name = dbus.service.BusName(bus_name, BUS) class Extension(Object): def __init__(self, parent, name): path = PATH_SEP.join((parent._path, name)) interface = path_to_bus_name(path) Object.__init__(self, BUS, interface, path) self._parent = parent """ def method(doodle='', out_signature='', interface=None): # two choices: # 1) called as @ipc.method if isinstance(doodle, FunctionType): doodle._ipc_name = doodle.__name__ doodle._ipc_in_signature = '' doodle._ipc_out_signature = '' doodle._ipc_expose = True doodle._ipc_interface = interface return doodle # 2) called as @ipc.method() else: in_signature = doodle def decorator(f): f._ipc_name = f.__name__ f._ipc_in_signature = in_signature f._ipc_out_signature = out_signature f._ipc_expose = True f._ipc_interface = interface return f return decorator python-cream-0.5.3/cream/ipc/hacks.py000066400000000000000000000063301217205071000174270ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import dbus import dbus.service def add_method(cls, func, *args, **kwargs): """ add a method to the dbus.service.Object subclass *cls*. *func* is the function (don't forget the `self` argument), all further arguments are passed verbatim to the `dbus.service.method` decorator function. This function will add *func* as an attribute to *cls*. """ func = dbus.service.method(*args, **kwargs)(func) name = func.__name__ # some dirty magic follows. # patch together some "qualificated" class name. # It consists of the module and the class name. clsname = '.'.join((cls.__module__, cls.__name__)) # dbus-python uses a magic `_dbus_class_table` class attribute # for storing the methods. It contains some nested dictionaries. # The keys are the class names, and the values are dictionaries # mapping interface names to dictionaries that map method names # to Python functions. Wow. # So, we add the new method to the class table here. entry = cls._dbus_class_table.setdefault(clsname, {}) # We just steal the name of the interface from the function. # `dbus.service.method` added it for us. entry.setdefault(func._dbus_interface, {})[name] = func # Finally we set it as an attribute. setattr(cls, name, func) def create_signal_emitter(name, signature, interface, argnames): f = lambda *args: None f.__name__ = name f._dbus_is_signal = True f._dbus_interface = interface f._dbus_signature = signature f._dbus_args = argnames return f def add_signal(cls, name, signature, interface): """ adds a signal named *name* with the signature *signature* to the dbus class *cls*. If you call the signal emitter, nothing will happen. You have to do that yourself. """ # get the argcount from the signature signal_argcount = len(list(dbus.Signature(signature))) # len(dbus.Signature(...)) gives strange results argnames = ['arg%d' % i for i in xrange(signal_argcount + 1)] func = create_signal_emitter(name, signature, interface, argnames) # now, essentially the same dirty magic as in `add_function` # is done. See above for explanation. clsname = '.'.join((cls.__module__, cls.__name__)) entry = cls._dbus_class_table.setdefault(clsname, {}) entry.setdefault(func._dbus_interface, {})[name] = func setattr(cls, name, func) python-cream-0.5.3/cream/ipc/properties.py000066400000000000000000000022731217205071000205340ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import dbus INTERFACE = 'org.freedesktop.DBus.Properties' def get(self, interface, name): return self.Get(interface, name, dbus_interface=INTERFACE) def set(self, interface, name, value): self.Set(interface, name, value, dbus_interface=INTERFACE) def get_all(self, interface): return self.GetAll(interface, dbus_interface=INTERFACE) python-cream-0.5.3/cream/ipc/tools.py000066400000000000000000000017661217205071000175060ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. def bus_name_to_path(bus_name): return '/' + bus_name.replace('.', '/') def path_to_bus_name(path): return path.replace('/', '.').strip('.') python-cream-0.5.3/cream/log.py000066400000000000000000000043131217205071000163430ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from time import strftime import os import sys from cream.util import console DEBUG = 5 INFO = 4 WARNING = 3 ERROR = 2 FATAL = 1 VERBOSITY = 3 COLORS = { DEBUG: console.COLOR_YELLOW, INFO: console.COLOR_GREEN, WARNING: console.COLOR_BLUE, ERROR: console.COLOR_RED, FATAL: console.COLOR_RED_BOLD, } SHORTS = { DEBUG: 'DBG', INFO: 'INF', WARNING: 'WRN', ERROR: 'ERR', FATAL: 'FTL', } class Messages(object): def __init__(self, id=None): self.id = id self.verbosity = int(os.getenv('CREAM_VERBOSITY', '0')) or VERBOSITY def debug(self, message): self.process_message(DEBUG, message) def info(self, message): self.process_message(INFO, message) def warning(self, message): self.process_message(WARNING, message) def error(self, message): self.process_message(ERROR, message) def fatal(self, message): self.process_message(FATAL, message) def process_message(self, type, message): if self.verbosity >= type: s = '%(color)s [%(short)s @ %(time)s] %(message)s%(uncolor)s' % { 'color' : COLORS[type], 'short' : SHORTS[type], 'time' : strftime("%H:%M:%S"), 'message' : message, 'uncolor' : console.COLOR_NORMAL } print s python-cream-0.5.3/cream/manifest.py000066400000000000000000000167631217205071000174040ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import itertools from lxml.etree import parse as parse_xml_file import cream.util MANIFEST_FILE = 'manifest.xml' class ManifestException(BaseException): pass class NoNamespaceDefinedException(BaseException): pass class Manifest(dict): def __init__(self, path, expand_paths=True): dict.__init__(self) self._path = path self._tree = parse_xml_file(self._path) self['path'] = os.path.dirname(os.path.abspath(path)) root = self._tree.getroot() if root.tag != 'manifest': raise ManifestException("Manifest root tag has to 'manifest'") namespaces = [] def append_ns(e): if e.get('namespace'): namespaces.append(e.get('namespace')) def remove_ns(e): if e.get('namespace'): namespaces.remove(e.get('namespace')) def expand_ns(s): if s.startswith('.'): if len(namespaces): return namespaces[-1] + s else: raise NoNamespaceDefinedException else: return s def expand_path(p): if expand_paths: if p: return os.path.join(os.path.dirname(self._path), p) else: return p # TODO: Use a bottom-down iteration here and lookup node handlers # from a dict or so. Much faster! append_ns(root) component = root.find('component') append_ns(component) # General meta information: self['id'] = expand_ns(component.get('id')) self['type'] = expand_ns(component.get('type')) self['name'] = component.get('name') self['version'] = component.get('version') self['exec'] = component.get('exec') # Licenses: self['licenses'] = [] licenses = component.findall('license') for license in licenses: append_ns(license) self['licenses'].append({ 'title' : license.get('title'), 'version' : license.get('version') }) remove_ns(license) # Icon: icon = component.find('icon') if icon is not None: append_ns(icon) self['icon'] = expand_path(icon.get('path')) remove_ns(icon) # Category self['categories'] = [] categories = component.findall('category') for category in categories: append_ns(category) self['categories'].append({ 'id' : expand_ns(category.get('id')) }) remove_ns(category) # Descriptions: self['descriptions'] = {} descriptions = component.findall('description') for descr in descriptions: append_ns(descr) self['descriptions'][descr.get('lang')] = descr.get('content') remove_ns(descr) self['description'] = self['descriptions'].get('en') or '' # Authors: self['authors'] = [] authors = component.findall('author') for author in authors: append_ns(author) self['authors'].append({ 'name': author.get('name'), 'type': author.get('type'), 'mail': author.get('mail') }) remove_ns(author) # Features: self['features'] = [] features = component.findall('use-feature') for feature in features: append_ns(feature) feature_args = {} for k, v in feature.attrib.iteritems(): if not k in ['id']: feature_args[k] = v self['features'].append( (expand_ns(feature.attrib.pop('id')), feature_args) ) remove_ns(feature) # Dependencies: self['dependencies'] = [] dependencies = component.findall('dependency') for dependency in dependencies: append_ns(dependency) self['dependencies'].append({ 'id' : expand_ns(dependency.get('id')), 'type' : expand_ns(dependency.get('type')), 'required' : dependency.get('required') }) remove_ns(dependency) # Provided component types: self['provided-components'] = [] provided_components = component.findall('provide-component') for component in provided_components: append_ns(component) self['provided-components'].append(expand_ns(component.get('type'))) remove_ns(component) # Package information: package = root.find('package') if package is None: return self['package'] = {} self['package']['auto'] = package.get('auto') == 'true' self['package']['rules'] = { 'ignore': [], 'application': [], 'desktop': [], 'icon': [], 'library': [], } rules = package.findall('rule') for rule in rules: type = rule.get('type') files = rule.get('files') self['package']['rules'][type].append(files) def __str__(self): return "".format(self._path) class ManifestDB(object): def __init__(self, paths, type=None): if isinstance(paths, basestring): self.paths = [paths] else: self.paths = paths self.type = type self.manifests = {} self._manifest_scanner = self.scan() def scan(self): for path in self.paths: for file_ in cream.util.walkfiles(os.path.abspath(path)): filename = os.path.split(file_)[1] if filename == MANIFEST_FILE: manifest = Manifest(file_) if not self.type or manifest['type'] == self.type: self.manifests[manifest['id']] = manifest yield manifest def get(self, **kwargs): if 'id' in kwargs and kwargs['id'] in self.manifests: return self.manifests[kwargs['id']] for manifest in self.manifests.itervalues(): for key, value in kwargs.iteritems(): if manifest.get(key, None) == value: return manifest for manifest in self._manifest_scanner: for key, value in kwargs.iteritems(): if manifest.get(key, None) == value: return manifest def get_all(self): # load all manifests for manifest in self._manifest_scanner: pass return self.manifests.values() python-cream-0.5.3/cream/path.py000066400000000000000000000026571217205071000165270ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os try: XDG_DATA_DIRS = os.environ['XDG_DATA_DIRS'].split(':') except KeyError: XDG_DATA_DIRS = ['/usr/share'] try: XDG_DATA_HOME = os.environ['XDG_DATA_HOME'].split(':') except KeyError: XDG_DATA_HOME = [os.path.expanduser('~/.local/share')] CREAM_DATA_DIR = XDG_DATA_DIRS[0] CREAM_DATA_HOME = XDG_DATA_HOME[0] CREAM_DATA_DIRS = XDG_DATA_DIRS + XDG_DATA_HOME VIRTUALENV_DATA_HOME = '' virtual_env = os.environ.get('VIRTUAL_ENV', False) if virtual_env: VIRTUALENV_DATA_HOME = os.path.join(virtual_env, 'share/') CREAM_DATA_DIRS.append(VIRTUALENV_DATA_HOME) python-cream-0.5.3/cream/util/000077500000000000000000000000001217205071000161645ustar00rootroot00000000000000python-cream-0.5.3/cream/util/__init__.py000066400000000000000000000106331217205071000203000ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. def set_process_name(name): from procname import setprocname return setprocname(name) def get_process_name(): from procname import getprocname return getprocname() def get_source_file(object): from inspect import getsourcefile return getsourcefile(object) def joindir(_file, *parts): import os return os.path.join(os.path.dirname(os.path.abspath(_file)), *parts) def walkfiles(*args, **kwargs): import os for directory, directories, files in os.walk(*args, **kwargs): for file_ in files: yield os.path.join(directory, file_) def urljoin_multi(*parts): """ Joins multiple strings into an url using a slash ('/'). Example:: >>> urljoin_multi('http://cream-project.org', 'is', 'g', 'reat') http://cream-project.org/is/g/reat >>> urljoin_multi('http://cream-project.org', 'is', 'g/', 'reat/') http://cream-project.org/is/g/reat/ """ from urlparse import urljoin s = reduce(lambda a,b: urljoin(a, b).rstrip('/')+'/', parts) if not parts[-1].endswith('/'): s = s[:-1] return s def extend_querystring(url, params): """ Extends the querystring of an given url. Example:: >>> extend_querystring('http://cream-project.org', {'foo': 'bar'}) http://cream-project.org?foo=bar >>> extend_querystring('http://cream-project.org?id=1', {'foo': 'bar', 'type': 'awesome'}) http://cream-project.org?id=1&foo=bar&type=awesome """ import urllib import urlparse url_parts = list(urlparse.urlparse(url)) query = dict(urlparse.parse_qsl(url_parts[4])) query.update(params) url_parts[4] = urllib.urlencode(query) return urlparse.urlunparse(url_parts) def isiterable(iterable, include_strings=False, include_dicts=True): if isinstance(iterable, basestring): return include_strings if isinstance(iterable, dict): return include_dicts try: iter(iterable) return True except TypeError: return False def flatten(iterable, n=None, level=0): """ Flatten a list or tuple. If `n` is set, stop at level `n`. Returns a generator. """ if n is not None and level >= n: # reached max. level, don't flatten anymore yield iterable return for item in iterable: if isiterable(item, include_dicts=False): for subitem in flatten(item, n=n, level=level+1): yield subitem else: yield item class cached_property(object): # taken from werkzeug (BSD license, original author: Armin Ronacher) not_none = False # if `not_none` is set, `cached_property` won't accept `None` as valid # `func` return value but will stay and wait for some non-`None` value. def __new__(cls, *args, **kwargs): if not args: from functools import partial return partial(cls, *args, **kwargs) else: return super(cls, cls).__new__(cls) def __init__(self, func, name=None, doc=None, not_none=False): self.func = func self.__name__ = name or func.__name__ self.__doc__ = doc or func.__doc__ self.not_none = not_none def __get__(self, obj, type=None): if obj is None: return self value = self.func(obj) if not (self.not_none and value is None): setattr(obj, self.__name__, value) return value def random_hash(bits=100, hashfunction='sha256'): from random import getrandbits import hashlib return getattr(hashlib, hashfunction)(str(getrandbits(bits))).hexdigest() python-cream-0.5.3/cream/util/console.py000066400000000000000000000027311217205071000202030ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. COLOR_RED = "\033[31m" COLOR_RED_BOLD = "\033[1;31m" COLOR_GREEN = "\033[32m" COLOR_BLUE = "\033[34m" COLOR_PURPLE = "\033[35m" COLOR_CYAN = "\033[36m" COLOR_YELLOW = "\033[33m" COLOR_GREY = "\033[37m" COLOR_BLACK = "\033[30m" COLOR_NORMAL = "\033[0m" def colorized(string, color): """ Returns ``string`` in ``color`` """ return color + string + COLOR_NORMAL def get_tty_size(): import subprocess p = subprocess.Popen(('stty', 'size'), stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) size = p.stdout.readlines()[0].strip().split(' ') return map(int, reversed(size)) python-cream-0.5.3/cream/util/dicts.py000066400000000000000000000103641217205071000176500ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. from UserDict import DictMixin from collections import defaultdict try: from collections import OrderedDict as ordereddict except ImportError: class ordereddict(dict, DictMixin): def __init__(self, *args, **kwds): if len(args) > 1: raise TypeError('expected at most 1 arguments, got %d' % len(args)) try: self.__end except AttributeError: self.clear() self.update(*args, **kwds) def clear(self): self.__end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.__map = {} # key --> [key, prev, next] dict.clear(self) def __setitem__(self, key, value): if key not in self: end = self.__end curr = end[1] curr[2] = end[1] = self.__map[key] = [key, curr, end] dict.__setitem__(self, key, value) def __delitem__(self, key): dict.__delitem__(self, key) key, prev, next = self.__map.pop(key) prev[2] = next next[1] = prev def __iter__(self): end = self.__end curr = end[2] while curr is not end: yield curr[0] curr = curr[2] def __reversed__(self): end = self.__end curr = end[1] while curr is not end: yield curr[0] curr = curr[1] def popitem(self, last=True): if not self: raise KeyError('dictionary is empty') key = reversed(self).next() if last else iter(self).next() value = self.pop(key) return key, value def __reduce__(self): items = [[k, self[k]] for k in self] tmp = self.__map, self.__end del self.__map, self.__end inst_dict = vars(self).copy() self.__map, self.__end = tmp if inst_dict: return (self.__class__, (items,), inst_dict) return self.__class__, (items,) def keys(self): return list(self) setdefault = DictMixin.setdefault update = DictMixin.update pop = DictMixin.pop values = DictMixin.values items = DictMixin.items iterkeys = DictMixin.iterkeys itervalues = DictMixin.itervalues iteritems = DictMixin.iteritems def __repr__(self): if not self: return '%s()' % (self.__class__.__name__,) return '%s(%r)' % (self.__class__.__name__, self.items()) def copy(self): return self.__class__(self) @classmethod def fromkeys(cls, iterable, value=None): d = cls() for key in iterable: d[key] = value return d def __eq__(self, other): if isinstance(other, ordereddict): return len(self)==len(other) and \ all(p==q for p, q in zip(self.items(), other.items())) return dict.__eq__(self, other) def __ne__(self, other): return not self == other class ordereddefaultdict(ordereddict, defaultdict): def __init__(self, default_factory, *args, **kwargs): defaultdict.__init__(self, default_factory) ordereddict.__init__(self, *args, **kwargs) python-cream-0.5.3/cream/util/events.py000066400000000000000000000027261217205071000200510ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os, select class Event(object): def __init__(self): self._read_fd, self._write_fd = os.pipe() def wait(self, timeout=None): rfds, wfds, efds = select.select([self._read_fd], [], [], timeout) return self._read_fd in rfds def is_set(self): return self.wait(0) def clear(self): if self.is_set(): os.read(self._read_fd, 1) def set(self): if not self.is_set(): os.write(self._write_fd, '1') def fileno(self): return self._read_fd def __del__(self): os.close(self._read_fd) os.close(self._write_fd) python-cream-0.5.3/cream/util/procname.py000066400000000000000000000036161217205071000203500ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import ctypes libc = ctypes.CDLL(None) strlen = libc.strlen strlen.argtypes = [ctypes.c_void_p] strlen.restype = ctypes.c_size_t memset = libc.memset memset.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_size_t] memset.restype = ctypes.c_void_p PR_SET_NAME = 15 PR_GET_NAME = 16 prctl = libc.prctl prctl.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_ulong, ctypes.c_ulong, ctypes.c_ulong] # we don't want no type safety prctl.restype = ctypes.c_int def _get_argc_argv(): argv = ctypes.POINTER(ctypes.POINTER(ctypes.c_char))() argc = ctypes.c_int() ctypes.pythonapi.Py_GetArgcArgv(ctypes.byref(argc), ctypes.byref(argv)) return (argc, argv) def getprocname(): argc, argv = _get_argc_argv() return ctypes.cast(argv[0], ctypes.c_char_p).value def setprocname(name): argc, argv = _get_argc_argv() libc.strncpy(argv[0], name, len(name)) next = ctypes.addressof(argv[0].contents) + len(name) nextlen = strlen(next) libc.memset(next, 0, nextlen) if prctl(PR_SET_NAME, name, 0, 0, 0) != 0: raise OSError() python-cream-0.5.3/cream/util/string.py000066400000000000000000000025171217205071000200510ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. def slugify(s): """ Returns a slug-ready version of ``s``. (Lowercases all letters, replaces non-alphanumeric letters with hyphens, replaces all whitespace with underscores) """ import re return re.sub('[^a-z0-9]', '-', re.sub('\s', '_', s.lower())) def crop_string(s, maxlen, appendix='...'): """ Crop string ``s`` after ``maxlen`` chars and append ``appendix``. """ if len(s) > maxlen: return s[:maxlen] + appendix else: return s python-cream-0.5.3/cream/util/subprocess.py000066400000000000000000000057131217205071000207340ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import sys from gi.repository import GObject as gobject class Subprocess(gobject.GObject): """ GObject API for handling child processes. :param command: The command to be run as a subprocess. :param fork: If `True` this process will be detached from its parent and run independent. This means that no excited-signal will be emited. :type command: `list` :type fork: `bool` """ __gtype_name__ = 'Subprocess' __gsignals__ = { 'exited': (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT, gobject.TYPE_INT)) } def __init__(self, command, name=None, fork=False): gobject.GObject.__init__(self) self.process = None self.pid = None if not fork: self.stdout = True self.stderr = True else: self.stdout = False self.stderr = False self.command = command self.name = name self.forked = fork if fork: self.fork() def run(self): """ Run the process. """ process_data = gobject.spawn_async(self.command, flags=gobject.SPAWN_SEARCH_PATH|gobject.SPAWN_DO_NOT_REAP_CHILD, standard_output=self.stdout, standard_error=self.stderr ) self.pid = process_data[0] self.stdout = os.fdopen(process_data[2]) self.stderr = os.fdopen(process_data[3]) print self.stderr self.watch = gobject.child_watch_add(self.pid, self.exited_cb) return self.pid def exited_cb(self, pid, condition): if not self.forked: self.emit('exited', pid, condition) def fork(self): try: # first fork pid = os.fork() if pid > 0: sys.exit(0) except OSError, e: sys.exit(1) os.chdir("/") os.setsid() os.umask(0) try: # second fork pid = os.fork() if pid > 0: sys.exit(0) except OSError, e: sys.exit(1) python-cream-0.5.3/cream/util/unique.py000066400000000000000000000257341217205071000200570ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. """ Unique protocol --------------- When a client connects: Server accepts request. Client sends `ping` message. Server responds with `pong` message. Client sends `notify` message. Server responds with `kthxbai` message. Client responds with `cu` message and dies. """ import os import socket from lxml import etree from gi.repository import GObject as gobject, GLib as glib from cream.util.xmlserialize import serialize, unserialize SOCKET_TEMPLATE = os.path.expanduser('~/.local/var/run/cream/%s.sock') PONG_TIMEOUT = 100 # = 100 ms. class UniqueApplication(gobject.GObject): def __init__(self, ident): gobject.GObject.__init__(self) self._ident = ident self._setup_unique() def _setup_unique(self): self._unique_manager = UniqueManager.get(self, self._ident) self._unique_manager.run() # TODO def _replace_server(self): """ I was a client before, but now I am a server. YAY! """ self._unique_manager.quit() self._setup_unique() def quit(self): self._unique_manager.quit() gobject.type_register(UniqueApplication) gobject.signal_new('already-running', UniqueApplication, gobject.SIGNAL_RUN_LAST, \ gobject.TYPE_PYOBJECT, ()) gobject.signal_new('start-attempt', UniqueApplication, gobject.SIGNAL_RUN_LAST, \ gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) class UniqueManager(object): @classmethod def get(cls, app, ident): """ :param ident: A string to identify an application. Should be unique system-wide. """ socket_path = SOCKET_TEMPLATE % ident if os.path.exists(socket_path): # This application is already running. I'm a client. return UniqueManagerClient(app, ident) else: # I'm the server. return UniqueManagerServer(app, ident) def __init__(self, app, ident): self.sources = set() self.app = app self.ident = ident self.socket = None self.socket_path = SOCKET_TEMPLATE % ident def run(self): raise NotImplementedError() def quit(self): try: self.socket.close() except socket.error: pass # remove all glib sources for source in self.sources: glib.source_remove(source) def build_message(type, node=None): """ Return a XML-serialized message as a string. Its type is *type*, its content node is *node*. """ msg = etree.Element('message', type=type) if node is not None: msg.append(node) return etree.tostring(msg) def serialize_message(type, obj): return build_message(type, serialize(obj, return_string=False)) class States(object): NONE = 'none' HANDSHAKE_DONE = 'handshake done' DONE = 'done' class UniqueError(Exception): pass class Handler(object): def __init__(self): self.buffer = '' def handle(self, node): raise NotImplementedError() def handle_message(self, message): # It's XML, parse it. node = etree.fromstring(message) self.handle(node) def handle_data(self): """ I have new data. Read it. """ # Read all available data. while True: try: new = self.conn.recv(1024) if not new: break self.buffer += new except socket.error: # TODO: ouch? break # Split at nullbyte messages = self.buffer.split('\0') # Last element isn't empty? So we received an incomplete message, # leave the last element in the buffer. Otherwise, clear it. self.buffer = messages.pop() # Handle all messages. for message in messages: self.handle_message(message) class Client(Handler): def __init__(self, manager, conn): Handler.__init__(self) self.manager = manager self.conn = conn self.state = States.NONE self.buffer = '' self.sources = set() def handle_ping(self, node): self.expect_type(node, 'ping') # send a `pong` message. self.send_message('pong') # and set the new state. self.state = States.HANDSHAKE_DONE def remove(self): for source in self.sources: glib.source_remove(source) def handle_notify(self, node): self.expect_type(node, 'notify') # get the data. data = unserialize(node[0]) # emit the signal. self.manager.app.emit('start-attempt', data) # send the message. self.send_message('kthxbai') self.state = States.DONE def handle_argh(self, node): self.expect_type(node, 'cu') # self-destruct. self.conn.close() self.manager.remove_client(self) def expect_type(self, node, type): if node.attrib.get('type') != type: raise UniqueError('%r = stupid client. Expected %s, got %s' % (self, type, node.attrib.get('type'))) def handle(self, node): { States.NONE: self.handle_ping, States.HANDSHAKE_DONE: self.handle_notify, States.DONE: self.handle_argh, }[self.state](node) def send(self, text): """ Send *text* and end with a null byte. """ self.conn.sendall('%s\0' % text) def send_message(self, type, node=None): self.send(build_message(type, node)) class UniqueManagerServer(UniqueManager): def __init__(self, app, ident): UniqueManager.__init__(self, app, ident) self.clients = {} # { conn: client } def quit(self): UniqueManager.quit(self) if os.path.exists(self.socket_path): os.remove(self.socket_path) def run(self): # Create a UNIX socket if not os.path.exists(os.path.dirname(self.socket_path)): os.makedirs(os.path.dirname(self.socket_path)) self.socket = socket.socket(socket.AF_UNIX) self.socket.setblocking(False) self.socket.bind(self.socket_path) # Doit! self.socket.listen(1) # Connect IO callbacks self.sources.add(glib.io_add_watch(self.socket, glib.IO_IN, self._listen_callback)) def _listen_callback(self, source, condition): conn, addr = self.socket.accept() conn.setblocking(False) # add the client client = Client(self, conn) self.add_client(client) # I want to get the data. client.sources.add(glib.io_add_watch(conn, glib.IO_IN | glib.IO_HUP, self._data_callback)) return True def add_client(self, client): self.clients[client.conn] = client def remove_client(self, client): client.remove() del self.clients[client.conn] def get_client_by_conn(self, conn): return self.clients[conn] def _data_callback(self, source, condition): # HUP? So our client disappeared. Sad thing. client = self.get_client_by_conn(source) if condition & glib.IO_HUP: source.close() self.remove_client(client) return False else: client.handle_data() return True class UniqueManagerClient(UniqueManager, Handler): def __init__(self, app, ident): UniqueManager.__init__(self, app, ident) Handler.__init__(self) self.handshake_done = False def send(self, text): """ Send *text* and end with a null byte. """ self.socket.sendall('%s\0' % text) def send_message(self, type, node=None): self.send(build_message(type, node)) def send_serialized_message(self, type, obj): self.send(serialize_message(type, obj)) def send_ping(self): self.send_message('ping') # if we haven't received a pong in $PONG_TIMEOUT ms, # consider the server down. gobject.timeout_add(PONG_TIMEOUT, self.check_handshake) def check_handshake(self): """ Do we have a valid handshake yet? If not, the server is down. Please replace. Otherwise, raise the `already-running` signal. """ if not self.handshake_done: self.replace_server() else: self.already_running() return False def replace_server(self): """ For some reason, the server is offline. Replace it. """ # Kill the socket. :S if os.path.exists(self.socket_path): os.remove(self.socket_path) # Make my master replace me. :( self.app._replace_server() def already_running(self): result = self.app.emit('already-running') # serialize result, send notify message self.send_serialized_message('notify', result) def run(self): # Create a UNIX socket if not os.path.exists(os.path.dirname(self.socket_path)): os.makedirs(os.path.dirname(self.socket_path)) self.socket = self.conn = socket.socket(socket.AF_UNIX) # Set `self.conn` to make `Handler` happy self.socket.setblocking(False) try: self.socket.connect(self.socket_path) except socket.error: # Socket error ... uhm, replace it! self.replace_server() return # Connect IO callbacks self.sources.add(glib.io_add_watch(self.socket, glib.IO_IN, self._data_callback)) # Send ping message. self.send_ping() def _data_callback(self, source, condition): self.handle_data() return True def handle(self, node): { 'pong': self.handle_pong, 'kthxbai': self.handle_kthxbai, }[node.attrib['type']](node) def handle_pong(self, node): # handshake done! self.handshake_done = True def handle_kthxbai(self, node): # Server got our information, we can die now. self.send_message('cu') self.app.quit() python-cream-0.5.3/cream/util/xmlserialize.py000066400000000000000000000306451217205071000212560ustar00rootroot00000000000000# # Copyright (c) 2009, 2010 Jonas Haag . # All rights reserved. # License: 2-clause-BSD (Berkley Software Distribution) license # # http://github.com/jonashaag/xmlserialize.py # # The full text of the 2-clause BSD license follows. # # The 2-clause Berkley Software Distribution license # ================================================== # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. __all__ = ( 'NoSuchSerializer', 'NoSuchUnserializer', 'Serializer', 'GenericTypeSerializer', 'SimpleTypeSerializer', 'IntegerSerializer', 'FloatSerializer', 'LongSerializer', 'BooleanSerializer', 'StringSerializer', 'SimpleIterableSerializer', 'KeyValueIterableSerializer', 'RangeSerializer', 'serialize_atomic', 'serialize_to_file', 'unserialize_atomic', 'unserialize_file', 'unserialize_string', 'unserialize', 'serialize' ) try: from collections import defaultdict except ImportError: defaultdict = None try: from collections import OrderedDict except ImportError: OrderedDict = None from lxml.etree import ElementTree, Element, _ElementTree from lxml.etree import fromstring as elementtree_fromstring, tostring as elementtree_tostring def try_decode(s): try: return s.decode('utf-8') except UnicodeDecodeError, unicode_decode_error: try: import chardet except ImportError: raise unicode_decode_error else: encoding = chardet.detect(s)['encoding'] if encoding is not None: return s.decode(encoding) else: raise UnicodeDecodeError(s) class NoSuchSerializer(Exception): def __init__(self, object): Exception.__init__(self, "No serializer for type '%s' found" % type(object)) class NoSuchUnserializer(Exception): def __init__(self, type_): Exception.__init__(self, "No (un)serializer for type '%s' found" % type_) class PleaseUseUnicode(Exception): def __init__(self): Exception.__init__(self, "Can't serialize object of type 'str' -- please use unicode!") class _SerializerMeta(type): def __new__(cls, name, bases, dct): for method_name in ('unserialize', 'serialize'): if method_name in dct: dct[method_name] = classmethod(dct[method_name]) serializes = dct.setdefault('serializes', NotImplemented) if serializes is not NotImplemented: if not isinstance(serializes, (list, tuple)): dct['serializes'] = (serializes,) return type.__new__(cls, name, bases, dct) class Serializer(object): __metaclass__ = _SerializerMeta serializes = NotImplemented promote_lazy = False def serialize(cls, object, tag_name, serialize_as): raise NotImplementedError() def unserialize(cls, xml_element, unserialize_to): raise NotImplementedError() @classmethod def subclasses(cls): if not hasattr(cls, '_subclasses'): cls._subclasses = get_subclasses(cls, recursive=True) return cls._subclasses @classmethod def get_for_type(cls, type_, _cache={}): if isinstance(type_, Serializer): return type_ try: return _cache[type_] except KeyError: pass for subclass in cls.subclasses(): if subclass.serializes is NotImplemented: continue for serializes in subclass.serializes: if serializes is type_ or serializes.__name__ == type_: match = (serializes, subclass) _cache[type_] = match return match raise NoSuchUnserializer(type_ if isinstance(type_, str) else type_.__name__) class GenericTypeSerializer(Serializer): serializes = () # object promote_lazy = True class NoneTypeSerializer(Serializer): serializes = type(None) def serialize(cls, object, tag_name, serialize_as): return Element(tag_name, type='NoneType') def unserialize(cls, xml_element, unserialize_to): return None class SimpleTypeSerializer(Serializer): def unserialize(cls, xml_element, unserialize_to): assert xml_element.text is not None return unserialize_to(xml_element.text) def serialize(cls, object, tag_name, serialize_as): element = Element(tag_name, type=serialize_as.__name__) element.text = unicode(object) return element class IntegerSerializer(SimpleTypeSerializer): serializes = int class FloatSerializer(SimpleTypeSerializer): serializes = float class LongSerializer(SimpleTypeSerializer): serializes = long class BooleanSerializer(SimpleTypeSerializer): serializes = bool unserialize_map = { True : ('True', 'true', 1, '1'), False : ('False', 'false', 0, '0') } def unserialize(cls, xml_element, unserialize_to): if xml_element.text in cls.unserialize_map[True]: return True elif xml_element.text in cls.unserialize_map[False]: return False else: return bool(xml_element.text) class StringSerializer(SimpleTypeSerializer): serializes = (unicode, str) def serialize(cls, object, tag_name, serialize_as): if serialize_as is str: try: object = try_decode(object) except UnicodeDecodeError: raise PleaseUseUnicode() return super(cls, cls).serialize(unicode(object), tag_name, str) def unserialize(cls, xml_element, unserialize_to): if xml_element.text is None: return u'' return unicode(xml_element.text) import datetime import time class DateTimeSerializer(Serializer): serializes = datetime.datetime def serialize(cls, object, tag_name, serialize_as): element = Element(tag_name, type=serialize_as.__name__) element.text = str(time.mktime(object.timetuple())) return element def unserialize(cls, xml_element, unserialize_to): return datetime.datetime.fromtimestamp(float(xml_element.text)) class SimpleIterableSerializer(Serializer): serializes = (tuple, list, set, frozenset) item_key = 'item' def serialize(cls, object, tag_name, serialize_as): container_element = Element(tag_name, type=serialize_as.__name__) for item in object: container_element.append(serialize_atomic(item, cls.item_key)) return container_element def unserialize(cls, xml_element, unserialize_to): container = list() for child in xml_element.getchildren(): container.append(unserialize_atomic(child)) return unserialize_to(container) class KeyValueIterableSerializer(Serializer): serializes = (dict,) if defaultdict is not None: serializes += (defaultdict,) if OrderedDict is not None: serializes += (OrderedDict,) def serialize(cls, object, tag_name, serialize_as): container_element = Element(tag_name, type=serialize_as.__name__) for key, value in object.iteritems(): container_element.append(serialize_atomic(value, key)) return container_element def unserialize(cls, xml_element, unserialize_to): return cls.unserialize_tree(xml_element.getchildren(), unserialize_to) @classmethod def unserialize_tree(cls, xml_tree, unserialize_to, *args, **kwargs): container = unserialize_to() for child in xml_tree: object = unserialize_atomic(child, *args, **kwargs) container[child.tag] = object return container class RangeSerializer(Serializer): sep = ' to ' # TODO: step serializes = xrange def serialize(cls, range, tag_name, serialize_as): element = Element(tag_name, type=serialize_as.__name__) element.text = '%s%s%s' % (range[0], cls.sep, range[-1]+1) return element def unserialize(cls, xml_element, unserialize_to): return unserialize_to(*map(int, xml_element.text.split(cls.sep))) def get_subclasses(klass, recursive=False, max_depth=None, current_depth=0): subclasses = [] for subclass in klass.__subclasses__(): if subclass is not object: subclasses.append(subclass) if recursive: for subclass in get_subclasses(subclass, current_depth==max_depth, max_depth, current_depth+1): subclasses.append(subclass) return tuple(subclasses) def serialize_atomic(object, tag_name, _type_map_cache={}): if object is None: return NoneTypeSerializer.serialize(None, tag_name, None) if hasattr(object, '__xmlserialize__'): object = object.__xmlserialize__() object_type = type(object) match = _type_map_cache.get(object_type) if match is None: for serializer in Serializer.subclasses(): if serializer.serializes is NotImplemented: continue if object_type in serializer.serializes: match = (serializer, object_type) break if serializer.promote_lazy and match: # ignore greedy matchers if we got a better match continue for serializes in serializer.serializes: if issubclass(object_type, serializes): match = (serializer, serializes) if match is None: raise NoSuchSerializer(object) _type_map_cache[object_type] = match return match[0].serialize(object, tag_name, match[1]) def serialize(object, tag='object', root_tag=None, return_string=True): if root_tag: element_tree = Element(root_tag) element_tree.append(serialize_atomic(object, tag)) else: element_tree = serialize_atomic(object, tag) if return_string: return elementtree_tostring(element_tree) else: return element_tree def serialize_to_file(object, file_, *args, **kwargs): close = False if not isinstance(file_, file): file_ = open(file_, 'w') close = True file_.write(serialize(object, *args, **kwargs)) if close: file_.close() def unserialize_atomic(xml_element, typemap=None): object_type = xml_element.get('type') if typemap: object_type = typemap.get(object_type, object_type) object_type, unserializer = Serializer.get_for_type(object_type) return unserializer.unserialize(xml_element, object_type) def unserialize(xml_element_tree, has_root=True, *args, **kwargs): if has_root: if isinstance(xml_element_tree, _ElementTree): xml_element_tree = xml_element_tree.getiterator() else: xml_element_tree = xml_element_tree.getchildren() else: xml_element_tree = [xml_element_tree] return KeyValueIterableSerializer.unserialize_tree(xml_element_tree, dict, *args, **kwargs) def unserialize_file(file_, *args, **kwargs): close = False if not isinstance(file_, file): file_ = open(file_) close = True result = unserialize(elementtree_fromstring(file_.read()), *args, **kwargs) if close: file_.close() return result def unserialize_string(string, *args, **kwargs): return unserialize(elementtree_fromstring(string), *args, **kwargs) python-cream-0.5.3/cream/xdg/000077500000000000000000000000001217205071000157715ustar00rootroot00000000000000python-cream-0.5.3/cream/xdg/__init__.py000066400000000000000000000000001217205071000200700ustar00rootroot00000000000000python-cream-0.5.3/cream/xdg/desktopentries/000077500000000000000000000000001217205071000210345ustar00rootroot00000000000000python-cream-0.5.3/cream/xdg/desktopentries/__init__.py000066400000000000000000000076131217205071000231540ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os from glob import iglob from future_builtins import map from ConfigParser import ConfigParser, NoOptionError from functools import partial SECTION = 'Desktop Entry' DEFAULT_CATEGORIES = set(["AudioVideo", "Audio", "Video", "Development", "Education", "Game", "Graphics", "Network", "Office", "Settings", "System", "Utility"]) class DesktopEntry(ConfigParser): def __init__(self, filename): ConfigParser.__init__(self) self.filename = filename self.read(filename) def __repr__(self): return '' % (id(self), self.filename) @classmethod def get_all(cls, path='/usr/share/applications'): return map(DesktopEntry, iglob(os.path.join(path, '*.desktop'))) def get_default(self, key): return ConfigParser.get(self, SECTION, key) def get_bool(self, key): return ConfigParser.getboolean(self, SECTION, key) def has_option_default(self, key): return ConfigParser.has_option(self, SECTION, key) def get_strings(self, key, default=NotImplemented): if not self.has_option_default(key): return default else: return self.get_default(key).strip(';').split(';') # TODO: comma separated? def get_locale(self, key, locale=''): try: if not locale: return self.get_default(key) else: return self.get_default('%s[%s]' % key) except NoOptionError: return None type = property(partial(get_default, key='Type')) version = property(partial(get_default, key='Version')) name = property(partial(get_locale, key='Name')) generic_name = property(partial(get_locale, key='GenericName')) no_display = property(partial(get_bool, key='NoDisplay')) @property def recommended_category(self): for category in self.categories: if category in DEFAULT_CATEGORIES: return category if self.categories: return self.categories[0] else: return None comment = property(partial(get_locale, key='Comment')) icon = property(partial(get_locale, key='Icon')) hidden = property(partial(get_bool, key='Hidden')) only_show_in = property(partial(get_strings, key='OnlyShowIn')) not_show_in = property(partial(get_strings, key='NotShowIn')) try_exec = property(partial(get_default, key='TryExec')) exec_ = property(partial(get_default, key='Exec')) path = property(partial(get_default, key='Path')) terminal = property(partial(get_bool, key='Terminal')) mime_type = property(partial(get_strings, key='MimeType')) categories = property(partial(get_strings, key='Categories', default=())) startup_notify = property(partial(get_bool, key='StartupNotify')) startup_wmclass = property(partial(get_default, key='StartupWMClass')) url = property(partial(get_default, key='URL')) if __name__ == '__main__': import gtk from gtkmenu import to_gtk menu = to_gtk(DesktopEntry.get_all()) menu.popup(None, None, None, 1, 0) gtk.main() python-cream-0.5.3/cream/xdg/desktopentries/gtkmenu.py000066400000000000000000000066061217205071000230700ustar00rootroot00000000000000# Copyright: 2007-2013, Sebastian Billaudelle # 2010-2013, Kristoffer Kleine # This library is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. import os import re from operator import attrgetter, itemgetter from subprocess import Popen from collections import defaultdict import gtk KICK = re.compile('%[ifFuUck]') ICON_SIZE = 16 CATEGORY_ICONS = { "AudioVideo": 'applications-multimedia', "Audio": 'applications-multimedia', "Video": 'applications-multimedia', "Development": 'applications-development', "Education": 'applications-science', "Game": 'applications-games', "Graphics": 'applications-graphics', "Network": 'applications-internet', "Office": 'applications-office', "Settings": 'applications-engineering', "System": 'applications-system', "Utility": 'applications-other', } def activate_entry(widget, entry): exec_ = KICK.sub('', entry.exec_) if entry.terminal: term = os.environ.get('TERM', 'xterm') exec_ = '%s -e "%s"' % (term, exec_.encode('string-escape')) proc = Popen(exec_, shell=True) def lookup_icon(icon_name, size=ICON_SIZE): # I'd be so happy to use gtk.ICON_SIZE_MENU here, but it returns empty pixbufs sometimes. if os.path.isfile(icon_name): return gtk.gdk.pixbuf_new_from_file_at_size(icon_name, size, size) theme = gtk.icon_theme_get_default() icon_info = theme.lookup_icon(icon_name, size, 0) if icon_info: path = icon_info.get_filename() return gtk.gdk.pixbuf_new_from_file_at_size(path, size, size) else: return None def to_gtk(entries): tree = defaultdict(gtk.Menu) for entry in sorted(entries, key=attrgetter('name')): category = entry.recommended_category if not category: continue item = None if entry.icon: icon = lookup_icon(entry.icon) if icon is not None: item = gtk.ImageMenuItem() item.set_image(gtk.image_new_from_pixbuf(icon)) item.set_label(entry.name) if item is None: item = gtk.MenuItem(entry.name) item.connect('activate', activate_entry, entry) item.show() tree[category].append(item) menu = gtk.Menu() for category, submenu in sorted(tree.iteritems(), key=itemgetter(0)): icon = None if category in CATEGORY_ICONS: icon = lookup_icon(CATEGORY_ICONS[category]) item = gtk.ImageMenuItem(category) if icon is not None: item.set_image(gtk.image_new_from_pixbuf(icon)) item.set_submenu(submenu) item.show() menu.append(item) menu.show() return menu python-cream-0.5.3/setup.py000066400000000000000000000006701217205071000156350ustar00rootroot00000000000000from distutils.core import setup setup( name = 'python-cream', version = '0.5.3', author = 'The Cream Project (http://cream-project.org)', url = 'http://github.com/cream/python-cream', packages = [ 'cream', 'cream.gui', 'cream.config', 'cream.ipc', 'cream.util', 'cream.xdg', 'cream.xdg.desktopentries' ], package_data={'cream.config': ['interface/*']} )