diff -Nru cuttlefish-12.07.13/.bzr/README cuttlefish-12.08/.bzr/README --- cuttlefish-12.07.13/.bzr/README 2012-08-31 06:32:20.000000000 +0000 +++ cuttlefish-12.08/.bzr/README 1970-01-01 00:00:00.000000000 +0000 @@ -1,3 +0,0 @@ -This is a Bazaar control directory. -Do not change any files in this directory. -See http://bazaar.canonical.com/ for more information about Bazaar. diff -Nru cuttlefish-12.07.13/.bzr/branch/branch.conf cuttlefish-12.08/.bzr/branch/branch.conf --- cuttlefish-12.07.13/.bzr/branch/branch.conf 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/.bzr/branch/branch.conf 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -parent_location = bzr+ssh://bazaar.launchpad.net/~paolorotolo/ubuntu-app-reviews/cuttlefish/ diff -Nru cuttlefish-12.07.13/.bzr/branch/format cuttlefish-12.08/.bzr/branch/format --- cuttlefish-12.07.13/.bzr/branch/format 2012-08-31 06:32:21.000000000 +0000 +++ cuttlefish-12.08/.bzr/branch/format 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Bazaar Branch Format 7 (needs bzr 1.6) diff -Nru cuttlefish-12.07.13/.bzr/branch/last-revision cuttlefish-12.08/.bzr/branch/last-revision --- cuttlefish-12.07.13/.bzr/branch/last-revision 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/.bzr/branch/last-revision 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -5 paolorotolo@ubuntu-it.org-20120710153122-y9a301g1d9mmzgx3 diff -Nru cuttlefish-12.07.13/.bzr/branch/tags cuttlefish-12.08/.bzr/branch/tags --- cuttlefish-12.07.13/.bzr/branch/tags 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/.bzr/branch/tags 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -d8:12.07.1347:appbot@holba.ch-20120709151315-mgrs3rdym27k5btae \ No newline at end of file diff -Nru cuttlefish-12.07.13/.bzr/branch-format cuttlefish-12.08/.bzr/branch-format --- cuttlefish-12.07.13/.bzr/branch-format 2012-08-31 06:32:20.000000000 +0000 +++ cuttlefish-12.08/.bzr/branch-format 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Bazaar-NG meta directory, format 1 diff -Nru cuttlefish-12.07.13/.bzr/checkout/conflicts cuttlefish-12.08/.bzr/checkout/conflicts --- cuttlefish-12.07.13/.bzr/checkout/conflicts 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/.bzr/checkout/conflicts 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -BZR conflict list format 1 Binary files /tmp/MibGYED0Ra/cuttlefish-12.07.13/.bzr/checkout/dirstate and /tmp/lk5BLIWa1K/cuttlefish-12.08/.bzr/checkout/dirstate differ diff -Nru cuttlefish-12.07.13/.bzr/checkout/format cuttlefish-12.08/.bzr/checkout/format --- cuttlefish-12.07.13/.bzr/checkout/format 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/.bzr/checkout/format 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Bazaar Working Tree Format 6 (bzr 1.14) diff -Nru cuttlefish-12.07.13/.bzr/repository/format cuttlefish-12.08/.bzr/repository/format --- cuttlefish-12.07.13/.bzr/repository/format 2012-08-31 06:32:20.000000000 +0000 +++ cuttlefish-12.08/.bzr/repository/format 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Bazaar repository format 2a (needs bzr 1.16 or later) Binary files /tmp/MibGYED0Ra/cuttlefish-12.07.13/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.cix and /tmp/lk5BLIWa1K/cuttlefish-12.08/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.cix differ Binary files /tmp/MibGYED0Ra/cuttlefish-12.07.13/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.iix and /tmp/lk5BLIWa1K/cuttlefish-12.08/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.iix differ Binary files /tmp/MibGYED0Ra/cuttlefish-12.07.13/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.rix and /tmp/lk5BLIWa1K/cuttlefish-12.08/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.rix differ diff -Nru cuttlefish-12.07.13/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.six cuttlefish-12.08/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.six --- cuttlefish-12.07.13/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.six 2012-08-31 06:32:21.000000000 +0000 +++ cuttlefish-12.08/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.six 1970-01-01 00:00:00.000000000 +0000 @@ -1,5 +0,0 @@ -B+Tree Graph Index 2 -node_ref_lists=0 -key_elements=1 -len=0 -row_lengths= Binary files /tmp/MibGYED0Ra/cuttlefish-12.07.13/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.tix and /tmp/lk5BLIWa1K/cuttlefish-12.08/.bzr/repository/indices/e8fa8ebfbd9faaba002e432bc452fa18.tix differ Binary files /tmp/MibGYED0Ra/cuttlefish-12.07.13/.bzr/repository/pack-names and /tmp/lk5BLIWa1K/cuttlefish-12.08/.bzr/repository/pack-names differ Binary files /tmp/MibGYED0Ra/cuttlefish-12.07.13/.bzr/repository/packs/e8fa8ebfbd9faaba002e432bc452fa18.pack and /tmp/lk5BLIWa1K/cuttlefish-12.08/.bzr/repository/packs/e8fa8ebfbd9faaba002e432bc452fa18.pack differ diff -Nru cuttlefish-12.07.13/cuttlefish/CuttlefishWindow.py cuttlefish-12.08/cuttlefish/CuttlefishWindow.py --- cuttlefish-12.07.13/cuttlefish/CuttlefishWindow.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/CuttlefishWindow.py 2012-08-25 10:22:17.000000000 +0000 @@ -27,11 +27,17 @@ from cuttlefish.PreferencesCuttlefishDialog import PreferencesCuttlefishDialog from cuttlefish.reflexes import REFLEXES +from cuttlefish.plugins import MANAGER, EXAMPLE_CODE from cuttlefish.ui import ReflexUI, DynamicUI from cuttlefish.events import EventManager from cuttlefish.actions import ActionManager from cuttlefish.SelectDialog import SelectDialog -import cuttlefish +from cuttlefish.PluginbeginnerDialog import PluginbeginnerDialog +from cuttlefish.engine import ENGINE +import os +import os.path +from subprocess import Popen + # See cuttlefish_lib.Window.py for more details about how this class works @@ -41,6 +47,8 @@ def finish_initializing(self, builder): # pylint: disable=E1002 """Set up the main window""" super(CuttlefishWindow, self).finish_initializing(builder) + # hack to remove uneeded seperator + self.ui.helpMenu.get_children()[1].set_visible(False) self.AboutDialog = AboutCuttlefishDialog self.PreferencesDialog = PreferencesCuttlefishDialog @@ -61,11 +69,16 @@ self.ui.tvActions.append_column(column) self.ui.togEvent.connect('notify::active', self.on_togEvent_toggle) + self.ui.togNotification.connect('notify::active', self.on_togNotification_toggle) + self.ui.togIndicator.connect('notify::active', self.on_togIndicator_toggle) + self.ui.toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_PRIMARY_TOOLBAR) self.ui.tbActions.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR) self._toggleEmptyViewIfNeeded() self.connect('delete-event', self.on_delete_window) + def on_mnu_close_window_activate(self, widget, data=None): + self.on_delete_window(widget, data) def on_delete_window(self, widget, data=None): self.hide() @@ -87,13 +100,21 @@ self._editItem['event']['isActive'] = widget.get_active() self.ui.btnEvent.set_sensitive(self._editItem['event']['isActive']) self._eventUI.set_sensitive(self._editItem['event']['isActive']) + + + def on_togNotification_toggle(self, widget, data=None): + self._editItem['showNotification'] = widget.get_active() + + + def on_togIndicator_toggle(self, widget, data=None): + self._editItem['listInIndicator'] = widget.get_active() - def on_btnEvent_activate(self, widget, data=None): + def on_btnEvent_clicked(self, widget, data=None): dlg = SelectDialog() dlg.set_title("Please select an event type") model = self._eventMgr.get_liststore() - dlg.set_model(model) + dlg.set_models(model, self._eventMgr.get_categories()) idx = dlg.run() dlg.hide() @@ -108,25 +129,55 @@ self._editItem['event']['type'] = newType event = self._eventMgr.get_instance(self._editItem) - self.ui.btnEvent.set_label(event.get_name()) + self.ui.btnEvent.set_label(event.NAME) self._eventUI.clear() self._eventUI.setup(event) + def on_pluginsReload_activate(self, widget, data=None): + rid = self._editItem and self._editItem.record_id or None + + if rid: + self.on_tbGoBack_clicked(widget, data) + + ENGINE.teardown() + MANAGER.reload() + ENGINE.setup() + + if rid: + self.on_edit_clicked(widget, rid) + + + def on_pluginsEdit_activate(self, widget, data=None): + plugindir = os.path.expanduser('~/.cuttlefish/plugins') + if not os.path.exists(plugindir): + os.mkdir(plugindir) + + pluginfile = os.path.join(plugindir, 'myplugins.py') + if not os.path.isfile(pluginfile): + with open(pluginfile, 'w+') as f: + f.write(EXAMPLE_CODE) + dlg = PluginbeginnerDialog() + dlg.run() + dlg.destroy() + Popen(['gnome-open', pluginfile]) + + + # action conf def on_tbNewAction_clicked(self, widget, data=None): dlg = SelectDialog() dlg.set_title("Please select an action type") model = self._actionMgr.get_liststore() - dlg.set_model(model) + dlg.set_models(model, self._actionMgr.get_categories()) actionIdx = dlg.run() dlg.hide() if actionIdx > -1: action = self._actionMgr.get_class(model[actionIdx][0])() - self._editActions.append([action, action.get_name()]) + self._editActions.append([action, action.NAME]) self.ui.tvActions.set_cursor(len(self._editActions)-1) self._checkActionControls() @@ -200,10 +251,12 @@ self._editItem = self._reflexes.get(record_id) self.ui.txtName.set_text(self._editItem['name']) self.ui.togEvent.set_active(self._editItem['event']['isActive']) + self.ui.togNotification.set_active(self._editItem['showNotification']) + self.ui.togIndicator.set_active(self._editItem['listInIndicator']) self._eventUI.setup(self._eventMgr.get_instance(self._editItem)) event = self._eventMgr.get_class(self._editItem['event']['type']) - self.ui.btnEvent.set_label(event.get_name()) + self.ui.btnEvent.set_label(event.NAME) self._editAction = None self._editActions.clear() @@ -211,7 +264,7 @@ for i in xrange(len(self._editItem['actions'])): action = self._actionMgr.get_instance(self._editItem, i) - self._editActions.append([action, action.get_name()]) + self._editActions.append([action, action.NAME]) if i == 0: self._editAction = action self.ui.tvActions.set_cursor(0) diff -Nru cuttlefish-12.07.13/cuttlefish/PluginManager.py cuttlefish-12.08/cuttlefish/PluginManager.py --- cuttlefish-12.07.13/cuttlefish/PluginManager.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/PluginManager.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,28 +0,0 @@ -import os -import os.path -import sys -import inspect - - -def load_wd(subdir, parentClass): - base = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) - return load(base + '/' + subdir, parentClass) - - -def load(dir, parentClass): - result = {} - dir = os.path.abspath(os.path.expandvars(os.path.expanduser(dir))) - - if not os.path.isdir(dir): - return {} - - sys.path.append(dir) - for mod_file in [f for f in os.listdir(dir) if (not f.startswith('__')) and f.endswith('.py')]: - module = mod_file[:-3] - - __import__(module) - for cname, clazz in inspect.getmembers(sys.modules[module], inspect.isclass): - if not clazz is parentClass and parentClass in inspect.getmro(clazz): - result[cname] = clazz - - return result \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/PluginbeginnerDialog.py cuttlefish-12.08/cuttlefish/PluginbeginnerDialog.py --- cuttlefish-12.07.13/cuttlefish/PluginbeginnerDialog.py 1970-01-01 00:00:00.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/PluginbeginnerDialog.py 2012-08-25 10:22:17.000000000 +0000 @@ -0,0 +1,55 @@ +# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- +### BEGIN LICENSE +# Copyright (C) 2012 Alexander von Bremen-Kuehne +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 3, as published +# by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranties of +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +### END LICENSE + +from gi.repository import Gtk # pylint: disable=E0611 + +from cuttlefish_lib.helpers import get_builder + +import gettext +from gettext import gettext as _ +gettext.textdomain('cuttlefish') + +class PluginbeginnerDialog(Gtk.Dialog): + __gtype_name__ = "PluginbeginnerDialog" + + def __new__(cls): + """Special static method that's automatically called by Python when + constructing a new instance of this class. + + Returns a fully instantiated PluginbeginnerDialog object. + """ + builder = get_builder('PluginbeginnerDialog') + new_object = builder.get_object('pluginbeginner_dialog') + new_object.finish_initializing(builder) + return new_object + + def finish_initializing(self, builder): + """Called when we're finished initializing. + + finish_initalizing should be called after parsing the ui definition + and creating a PluginbeginnerDialog object with it in order to + finish initializing the start of the new PluginbeginnerDialog + instance. + """ + # Get a reference to the builder and set up the signals. + self.builder = builder + self.ui = builder.get_ui(self) + + +if __name__ == "__main__": + dialog = PluginbeginnerDialog() + dialog.show() + Gtk.main() diff -Nru cuttlefish-12.07.13/cuttlefish/PreferencesCuttlefishDialog.py cuttlefish-12.08/cuttlefish/PreferencesCuttlefishDialog.py --- cuttlefish-12.07.13/cuttlefish/PreferencesCuttlefishDialog.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/PreferencesCuttlefishDialog.py 2012-08-25 10:22:17.000000000 +0000 @@ -53,15 +53,18 @@ # Bind each preference widget to gsettings self._settings = Gio.Settings("net.launchpad.cuttlefish") - togNotifications = self.builder.get_object('togNotifications') - self._settings.connect("changed::notifications", self.on_notifications_changed) - self.ui.togNotifications.set_active(self._settings.get_boolean("notifications")) + self._settings.connect("changed::notifications", self.on_notifications_changed) self.ui.togNotifications.connect('notify::active', self.on_togNotifications_toggle) + self.ui.togNotifications.set_active(self._settings.get_boolean("notifications")) + + self._settings.connect("changed::logging", self.on_logging_changed) + self.ui.togLogging.connect('notify::active', self.on_togLogging_toggle) + self.ui.togLogging.set_active(self._settings.get_boolean("logging")) self._autostartFile = os.path.abspath(os.path.expanduser('~/.config/autostart/cuttlefish.desktop')) self._autostart = RawConfigParser() - + if os.path.isfile(self._autostartFile): self._autostart.read(self._autostartFile) else: @@ -86,6 +89,13 @@ def on_togNotifications_toggle(self, widget, data=None): self._settings.set_boolean('notifications', widget.get_active()) + + def on_logging_changed(self, settings, key): + self.ui.togLogging.set_active(settings.get_boolean("logging")) + + def on_togLogging_toggle(self, widget, data=None): + self._settings.set_boolean('logging', widget.get_active()) + def on_togAutostart_toggle(self, widget, data=None): if widget.get_active(): diff -Nru cuttlefish-12.07.13/cuttlefish/SelectDialog.py cuttlefish-12.08/cuttlefish/SelectDialog.py --- cuttlefish-12.07.13/cuttlefish/SelectDialog.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/SelectDialog.py 2012-08-25 10:22:17.000000000 +0000 @@ -48,28 +48,79 @@ self.builder = builder self.ui = builder.get_ui(self) - c = Gtk.CellRendererText() - self.ui.selGeneric.pack_start(c, True) - self.ui.selGeneric.add_attribute(c, 'text', 1) + self.ui.tvGeneric.append_column(Gtk.TreeViewColumn("Type", Gtk.CellRendererText(), text=1)) + self.ui.tvCategory.append_column(Gtk.TreeViewColumn("Category", Gtk.CellRendererText(), text=0)) + def set_title(self, title): self.ui.lblTitle.set_text(title) Gtk.Dialog.set_title(self, title) - def set_model(self, model): - self.ui.selGeneric.set_model(model) - self.ui.selGeneric.set_active(0) - + def set_models(self, model, cats): + self._catFilter = None + self._txtFilter = '' + self.ui.tvCategory.set_model(cats) + modelfilter = model.filter_new() + modelfilter.set_visible_func(self._filter) + self.ui.tvGeneric.set_model(modelfilter) + self.ui.tvGeneric.set_cursor(0) + self.ui.tvCategory.set_cursor(0) + + def _filter(self, model, iter, data=None): + isValid = True + if self._catFilter: + isValid = self._catFilter == model[iter][3] - def on_selGeneric_changed(self, widget, data=None): - model = self.ui.selGeneric.get_model() - idx = self.ui.selGeneric.get_active() - self.ui.lblDescp.set_text(model[idx][2]) + if isValid and self._txtFilter: + isValid = self._txtFilter.upper() in model[iter][1].upper() + + return isValid + + def _check_ok_btn(self): + path, col = self.ui.tvGeneric.get_cursor() + self.ui.btn_ok.set_sensitive(path != None) + + def on_tvCategory_cursor_changed(self, widget, data=None): + path, col = self.ui.tvCategory.get_cursor() + if path: + model = self.ui.tvCategory.get_model() + i = model.get_iter(path) + if model[i][0] == 'All Categories': + self._catFilter = None + else: + self._catFilter = model[i][0] + self.ui.tvGeneric.get_model().refilter() + self._check_ok_btn() + + def on_txtFilter_icon_release(self, widget, icon_pos, event, data=None): + self.ui.txtFilter.set_text('') + + def on_txtFilter_changed(self, widget, data=None): + self._txtFilter = self.ui.txtFilter.get_text() + self.ui.tvGeneric.get_model().refilter() + self._check_ok_btn() + + + def on_tvGeneric_cursor_changed(self, widget, data=None): + self._check_ok_btn() + path, col = self.ui.tvGeneric.get_cursor() + if path: + model = self.ui.tvGeneric.get_model() + i = model.get_iter(path) + self.ui.lblDescp.set_text(model[i][2]) def on_btn_ok_clicked(self, widget, data=None): - idx = self.ui.selGeneric.get_active() - self.response(idx) + path, col = self.ui.tvGeneric.get_cursor() + if path: + model = self.ui.tvGeneric.get_model() + i = model.get_iter(path) + + fullmodel = model.get_model() + fi = model.convert_iter_to_child_iter(i) + idx = fullmodel.get_path(fi).get_indices()[0] + + self.response(idx) def on_btn_cancel_clicked(self, widget, data=None): self.response(-1) diff -Nru cuttlefish-12.07.13/cuttlefish/__init__.py cuttlefish-12.08/cuttlefish/__init__.py --- cuttlefish-12.07.13/cuttlefish/__init__.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/__init__.py 2012-08-25 10:22:17.000000000 +0000 @@ -20,14 +20,37 @@ from gettext import gettext as _ gettext.textdomain('cuttlefish') -from gi.repository import Gtk, Gdk, GLib # pylint: disable=E0611 +from gi.repository import Gtk, Gdk, GLib, Gio # pylint: disable=E0611 + +import logging +from logging.handlers import RotatingFileHandler +logger = logging.getLogger('cuttlefish') +logger.setLevel(logging.DEBUG) +fh = RotatingFileHandler('/tmp/cuttlefish.log', maxBytes=100000, backupCount=5) +fh.setLevel(logging.DEBUG) +fh.setFormatter(logging.Formatter('[%(asctime)s] %(levelname)s in %(name)s: %(message)s')) + + +settings = Gio.Settings("net.launchpad.cuttlefish") + +def on_logging_changed(_settings, key): + if settings.get_boolean("logging"): + logger.addHandler(fh) + else: + logger.removeHandler(fh) + +settings.connect("changed::logging", on_logging_changed) +on_logging_changed(None, None) import dbus from dbus.mainloop.glib import DBusGMainLoop DBusGMainLoop(set_as_default=True) from cuttlefish import CuttlefishWindow -from cuttlefish.engine import Engine +from cuttlefish.plugins import MANAGER +MANAGER.reload() + +from cuttlefish.engine import ENGINE from cuttlefish_lib import set_up_logging, get_version GLib.threads_init() @@ -48,17 +71,15 @@ set_up_logging(options) return options -ENGINE = None def main(): 'constructor for your class instances' opts = parse_options() # Run the application. - global ENGINE - ENGINE = Engine() + ENGINE.setup() window = CuttlefishWindow.CuttlefishWindow() if not opts.hidden: window.show() Gtk.main() - ENGINE.interrupt() + ENGINE.teardown() diff -Nru cuttlefish-12.07.13/cuttlefish/actions.py cuttlefish-12.08/cuttlefish/actions.py --- cuttlefish-12.07.13/cuttlefish/actions.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/actions.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,7 +1,9 @@ from gi.repository import Gtk -from cuttlefish import PluginManager +from cuttlefish.plugins import CuttlePlugin, MANAGER class CuttleAction: + NAME = "UNKOWN" + DESCP = "No documnentation passed with this plugin." def __init__(self): self._params = self.get_default_params() @@ -35,23 +37,28 @@ pass -class ActionManager: - +class FallbackAction(CuttleAction, CuttlePlugin): def __init__(self): - self._actions = {} - self._actions.update(PluginManager.load('/usr/lib/python2.7/dist-packages/cuttlefish/plugins', CuttleAction)) - self._actions.update(PluginManager.load('/opt/extras.ubuntu.com/cuttlefish/cuttlefish/plugins', CuttleAction)) - self._actions.update(PluginManager.load('~/.cuttlefish/plugins', CuttleAction)) - + CuttleAction.__init__(self) + CuttlePlugin.__init__(self) + +class ActionManager: def get_liststore(self): - store = Gtk.ListStore(str, str, str) - for key in self._actions.keys(): - e = self._actions[key] - store.append([key,e.get_name(), e.get_descp()]) + store = Gtk.ListStore(str, str, str, str) + for cname, action in MANAGER.list(CuttleAction): + store.append([cname, action.NAME, action.DESCP, action.CATEGORY]) store.set_sort_column_id(1, Gtk.SortType.ASCENDING) return store + def get_categories(self): + store = Gtk.ListStore(str) + store.append(['All Categories']) + for cat in set([action.CATEGORY for key, action in MANAGER.list(CuttleAction)]): + store.append([cat]) + store.set_sort_column_id(0, Gtk.SortType.ASCENDING) + return store + def get_instance(self, record, idx): clazz = self.get_class(record['actions'][idx]['type']) instance = clazz() @@ -60,8 +67,5 @@ return instance def get_class(self, id): - if id in self._actions: - return self._actions[id] - else: - return CuttleAction + return MANAGER.get_class(CuttleAction, id, FallbackAction) diff -Nru cuttlefish-12.07.13/cuttlefish/engine.py cuttlefish-12.08/cuttlefish/engine.py --- cuttlefish-12.07.13/cuttlefish/engine.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/engine.py 2012-08-25 10:22:17.000000000 +0000 @@ -2,18 +2,20 @@ from cuttlefish.ui import ReflexUI from cuttlefish.events import EventManager from cuttlefish.actions import ActionManager +from cuttlefish.plugins import format_plugin_exc from cuttlefish_lib.helpers import get_media_file -from traceback import print_exc import threading -from gi.repository import Notify, Gio +from gi.repository import Notify, Gio, GObject Notify.init("cuttlefish") - +import logging class Handler(threading.Thread): - def __init__(self, actions, finished_callback): + def __init__(self, actions, finished_callback, reflex): threading.Thread.__init__(self) + self._logger = logging.getLogger('cuttlefish.engine.Handler') + self._reflex = reflex self._actions = actions self._dead = threading.Event() self._activeAction = None @@ -23,40 +25,37 @@ for action in self._actions: self._activeAction = action try: - self._activeAction.execute() - except: - print_exc() - - if self._dead.isSet(): - break + self._logger.info("Executing action '%s' for '%s'" % (action.NAME, self._reflex['name'])) + try: + action.execute() + except: + action.logger.error(format_plugin_exc()) + finally: + if self._dead.isSet(): + break if not self._dead.isSet(): + self._logger.info("Finished reaction for '%s'" % self._reflex['name']) self._finished_callback(threading.current_thread().getName()) - - def interrupt(self): self._dead.set() if self._activeAction: self._activeAction.interrupt() +class Engine(GObject.GObject): + __gsignals__ = { + 'startup': (GObject.SIGNAL_RUN_FIRST, None, ()), + 'shutdown': (GObject.SIGNAL_RUN_FIRST, None, ()) + } -class Engine: def __init__(self): + GObject.GObject.__init__(self) + self._logger = logging.getLogger('cuttlefish.engine.Engine') self._eventMgr = EventManager() self._actionMgr = ActionManager() self._reflexes = REFLEXES - self._events = {} - self._threads = {} self._settings = Gio.Settings("net.launchpad.cuttlefish") - - self._reflexes.connect('save', self.on_save) - self._reflexes.connect('delete', self.on_delete) - - self._events = {} - - for reflex in self._reflexes.list(): - self._add_reflex(reflex) def on_save(self, sender, reflex): if reflex.record_id in self._events: @@ -76,7 +75,9 @@ try: e.setup() except: - print_exc() + e.logger.error(format_plugin_exc()) + e.disconnect(e.hid) + del self._events[reflex.record_id] def _update_reflex(self, reflex): self._delete_reflex(reflex.record_id) @@ -86,23 +87,46 @@ def _delete_reflex(self, record_id): e = self._events[record_id] try: - e.teardown() - except: - print_exc() - e.disconnect(e.hid) - del self._events[record_id] - - def interrupt(self): - for rid, e in self._events.items(): try: e.teardown() except: - print_exc() + e.logger.error(format_plugin_exc()) + finally: + e.disconnect(e.hid) + del self._events[record_id] + + def setup(self): + self._threads = {} + self._events = {} + + for reflex in self._reflexes.list(): + self._add_reflex(reflex) + event = self._events[reflex.record_id] + if hasattr(event, 'triggerOnStartup') and hasattr(event.triggerOnStartup, '__call__'): + params = event.get_params() + if 'triggerOnStartup' in params and params['triggerOnStartup'] == True: + event.triggerOnStartup() + + + self._saveId = self._reflexes.connect('save', self.on_save) + self._delId = self._reflexes.connect('delete', self.on_delete) + self.emit('startup') + + + def teardown(self): + self.emit('shutdown') + + self._reflexes.disconnect(self._saveId) + self._reflexes.disconnect(self._delId) + + for rid, e in self._events.items(): + self._delete_reflex(rid) for tname, thread in self._threads.items(): thread.interrupt() + self._events = {} self._threads = {} @@ -113,17 +137,24 @@ def on_event_triggered(self, event, record_id, force=False): reflex = self._reflexes.get(record_id) if reflex['event']['isActive'] or force: - if self._settings.get_boolean("notifications"): + if force: + self._logger.info("Manually triggered reflex '%s'" % reflex['name']) + else: + self._logger.info("reflex '%s' triggered by '%s'" % (reflex['name'], event.NAME)) + + if self._settings.get_boolean("notifications") and reflex['showNotification']: n = Notify.Notification.new('Cuttlefish', reflex['name'], get_media_file("tentacle.png")) n.show() actions = [] for i in xrange(len(reflex['actions'])): actions.append(self._actionMgr.get_instance(reflex, i)) - t = Handler(actions, self.on_handle_finished) + t = Handler(actions, self.on_handle_finished, reflex) self._threads[t.getName()] = t t.start() def on_handle_finished(self, tname): if tname in self._threads: del self._threads[tname] + +ENGINE = Engine() \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/events.py cuttlefish-12.08/cuttlefish/events.py --- cuttlefish-12.07.13/cuttlefish/events.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/events.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,7 +1,10 @@ from gi.repository import GObject, Gtk -from cuttlefish import PluginManager +from cuttlefish.plugins import CuttlePlugin, MANAGER +import dbus + class CuttleEvent(GObject.GObject): + NAME = "None" __gsignals__ = { 'triggered': (GObject.SIGNAL_RUN_FIRST, None, ()) @@ -9,29 +12,6 @@ def __init__(self): GObject.GObject.__init__(self) - self._params = self.get_default_params() - - @classmethod - def get_name(clazz): - return 'None' - - @classmethod - def get_descp(clazz): - return 'No description given' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def get_params(self): - return self._params - - def set_params(self, params): - self._params.update(params) def setup(self): pass @@ -43,23 +23,58 @@ self.emit('triggered') -class EventManager: + +class DBusEvent(CuttleEvent): + USE_SYSTEM_BUS = 0 + USE_SESSION_BUS = 1 + + def __init__(self, callback, signal_name, dbus_interface, path, whichBus=USE_SESSION_BUS): + CuttleEvent.__init__(self) + self._callback = callback + self._signal_name = signal_name + self._dbus_interface = dbus_interface + self._path = path + if whichBus == DBusEvent.USE_SESSION_BUS: + self._bus = dbus.SessionBus() + else: + self._bus = dbus.SystemBus() + + def setup(self): + __str = lambda x : hasattr(x, '__call__') and x() or x + + self._hid = self._bus.add_signal_receiver(self._callback, + signal_name=__str(self._signal_name), + dbus_interface=__str(self._dbus_interface), + path=__str(self._path)) + def teardown(self): + self._bus.remove_signal_receiver(self._hid) + + +class FallbackEvent(CuttleEvent, CuttlePlugin): def __init__(self): - self._events = {} - self._events.update(PluginManager.load('/usr/lib/python2.7/dist-packages/cuttlefish/plugins', CuttleEvent)) - self._events.update(PluginManager.load('/opt/extras.ubuntu.com/cuttlefish/cuttlefish/plugins', CuttleEvent)) - self._events.update(PluginManager.load('~/.cuttlefish/plugins', CuttleEvent)) - + CuttleEvent.__init__(self) + CuttlePlugin.__init__(self) + +class EventManager: + def get_liststore(self): - store = Gtk.ListStore(str, str, str) - for key in self._events.keys(): - e = self._events[key] - store.append([key, e.get_name(), e.get_descp()]) + store = Gtk.ListStore(str, str, str, str) + for cname, event in MANAGER.list(CuttleEvent): + store.append([cname, event.NAME, event.DESCP, event.CATEGORY]) store.set_sort_column_id(1, Gtk.SortType.ASCENDING) return store + def get_categories(self): + store = Gtk.ListStore(str) + store.append(['All Categories']) + for cat in set([event.CATEGORY for key, event in MANAGER.list(CuttleEvent)]): + store.append([cat]) + store.set_sort_column_id(0, Gtk.SortType.ASCENDING) + return store + + def get_instance(self, record): clazz = self.get_class(record['event']['type']) @@ -69,7 +84,4 @@ return instance def get_class(self, id): - if id in self._events: - return self._events[id] - else: - return CuttleEvent \ No newline at end of file + return MANAGER.get_class(CuttleEvent, id, FallbackEvent) \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/indicator.py cuttlefish-12.08/cuttlefish/indicator.py --- cuttlefish-12.07.13/cuttlefish/indicator.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/indicator.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,4 +1,3 @@ -!/usr/bin/python # -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*- ### BEGIN LICENSE # Copyright (C) 2012 Alexander von Bremen-Kuehne @@ -22,7 +21,7 @@ from cuttlefish_lib.helpers import get_media_file from cuttlefish.reflexes import REFLEXES -import cuttlefish +from cuttlefish.engine import ENGINE import gettext from gettext import gettext as _ @@ -55,28 +54,29 @@ self.menu.append(menuShow) self.menu.append(Gtk.SeparatorMenuItem()) + self.menu.show_all() self._reflexes = {} + self._reflexSep = Gtk.SeparatorMenuItem() for b in REFLEXES.list(): self._add_reflex(b) REFLEXES.connect('save', self.on_save) REFLEXES.connect('delete', self.on_delete) - - self.menu.append(Gtk.SeparatorMenuItem()) + self.menu.append(self._reflexSep) self.quit = Gtk.MenuItem("Quit") self.quit.connect("activate",window.on_mnu_close_activate) + self.quit.show() self.menu.append(self.quit) # Add more items here - self.menu.show_all() self.indicator.set_menu(self.menu) def on_show_window_activate(self, widget, data=None): - self._window.show() + self._window.present() def on_save(self, sender, reflex): if reflex.record_id in self._reflexes: @@ -90,20 +90,26 @@ def _add_reflex(self, reflex): item = Gtk.MenuItem(reflex['name']) - item.connect("activate", cuttlefish.ENGINE.execute, reflex.record_id) - item.show() + item.connect("activate", ENGINE.execute, reflex.record_id) + if reflex['listInIndicator']: + item.show() self.menu.insert(item, 2) self._reflexes[reflex.record_id] = item + self._reflexSep.show() def _update_reflex(self, reflex): item = self._reflexes[reflex.record_id] item.set_label(reflex['name']) - + item.set_visible(reflex['listInIndicator']) + self._reflexSep.set_visible(any([item.get_visible() for rid, item in self._reflexes.items()])) + def _delete_reflex(self, record_id): self._reflexes[record_id].destroy() - del self._reflexes[record_id] + del self._reflexes[record_id] + self._reflexSep.set_visible(any([item.get_visible() for rid, item in self._reflexes.items()])) + def new_application_indicator(window): ind = Indicator(window) diff -Nru cuttlefish-12.07.13/cuttlefish/params.py cuttlefish-12.08/cuttlefish/params.py --- cuttlefish-12.07.13/cuttlefish/params.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/params.py 2012-08-25 10:22:17.000000000 +0000 @@ -3,6 +3,7 @@ import os.path from xdg import IconTheme + class Param: def __init__(self, label): self._label = Gtk.Label(label) @@ -23,7 +24,8 @@ def destroy(self): self._label.destroy() - self._widget.destroy() + if self._widget: + self._widget.destroy() def set_sensitive(self, sensitive): self._widget.set_sensitive(sensitive) @@ -56,34 +58,66 @@ def get_value(self): return self._widget.get_active() + class SelectParam(Param): def __init__(self, label, options, idxType=int): Param.__init__(self, label) self._model = Gtk.ListStore(idxType, str) - self._value2idx = {} - i = 0 + if hasattr(options, '__call__'): + self._reloadCallback = options + options = self._reloadCallback() + else: + self._reloadCallback = None + for value, label in options.items(): self._model.append([value, label]) - self._value2idx[value] = i - i += 1 + + + def _on_btnReload_click(self, widget, data=None): + tmp = self.get_value() + + self._model.clear() + for value, label in self._reloadCallback().items(): + self._model.append([value, label]) + + self.set_value(tmp) + def _create_widget(self): + hbox = Gtk.HBox() cb = Gtk.ComboBox() cb.set_model(self._model) cr = Gtk.CellRendererText() cr.props.ellipsize = Pango.EllipsizeMode.END cb.pack_start(cr, True) cb.add_attribute(cr, 'text', 1) - return cb + + hbox.add(cb) + if self._reloadCallback: + btnReload = Gtk.Button() + btnReload.set_image(Gtk.Image.new_from_stock(Gtk.STOCK_REFRESH, Gtk.IconSize.BUTTON)) + btnReload.connect('clicked', self._on_btnReload_click) + hbox.add(btnReload) + + return hbox def set_value(self, value): - if value in self._value2idx: - idx = self._value2idx[value] - self._widget.set_active(idx) + for idx, row in enumerate(self._model): + if value == row[0]: + self._widget.get_children()[0].set_active(idx) + break def get_value(self): - idx = self._widget.get_active() - return self._model[idx][0] + idx = self._widget.get_children()[0].get_active() + if idx >= 0: + return self._model[idx][0] + + def get_caption(self): + idx = self._widget.get_children()[0].get_active() + if idx < 0: + return '' + else: + return self._model[idx][1] class IntParam(Param): @@ -249,4 +283,98 @@ def get_value(self): idx = self._widget.get_active() - return self._model[idx][0] \ No newline at end of file + return self._model[idx][0] + + +class TimeParam(Param): + + def __init__(self, label): + Param.__init__(self, label) + + def _create_widget(self): + hbox = Gtk.HBox() + + sHour = Gtk.SpinButton() + sHour.set_adjustment(Gtk.Adjustment(0, 0, 60, 1, 0, 0)) + sHour.set_numeric(True) + sHour.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID) + hbox.add(sHour) + + sMin = Gtk.SpinButton() + sMin.set_adjustment(Gtk.Adjustment(0, 0, 60, 1, 0, 0)) + sMin.set_numeric(True) + sMin.set_update_policy(Gtk.SpinButtonUpdatePolicy.IF_VALID) + hbox.add(sMin) + + return hbox + + + def set_value(self, value): + hour, mins = value + sHour, sMin = self._widget.get_children() + sHour.set_value(int(hour)) + sMin.set_value(int(mins)) + + def get_value(self): + sHour, sMin = self._widget.get_children() + return (sHour.get_value(), sMin.get_value()) + + +class MultiSelectParam(Param): + def __init__(self, label, options, idxType=int): + Param.__init__(self, label) + self._model = Gtk.ListStore(idxType, str) + if hasattr(options, '__call__'): + self._reloadCallback = options + options = self._reloadCallback() + else: + self._reloadCallback = None + + for value, label in options.items(): + self._model.append([value, label]) + + + def _on_btnReload_click(self, widget, data=None): + tmp = self.get_value() + + self._model.clear() + for value, label in self._reloadCallback().items(): + self._model.append([value, label]) + + self.set_value(tmp) + + + def _create_widget(self): + hbox = Gtk.ScrolledWindow() + hbox.props.width_request = 150 + hbox.props.height_request = 100 + tv = Gtk.TreeView() + tv.props.headers_visible = False + tv.set_model(self._model) + tv.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) + cr = Gtk.CellRendererText() + column = Gtk.TreeViewColumn("Action", Gtk.CellRendererText(), text=1) + tv.append_column(column) + cr.props.ellipsize = Pango.EllipsizeMode.END + + hbox.add(tv) + if self._reloadCallback: + btnReload = Gtk.Button() + btnReload.set_image(Gtk.Image.new_from_stock(Gtk.STOCK_REFRESH, Gtk.IconSize.BUTTON)) + btnReload.connect('clicked', self._on_btnReload_click) + hbox.add(btnReload) + + return hbox + + def set_value(self, value): + sel = self._widget.get_children()[0].get_selection() + for v in value: + sel.select_path(v) + + def get_value(self): + model, paths = self._widget.get_children()[0].get_selection().get_selected_rows() + return [model[model.get_iter(path)][0] for path in paths] + + def get_captions(self): + model, paths = self._widget.get_children()[0].get_selection().get_selected_rows() + return [model[model.get_iter(path)][1] for path in paths] \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/__init__.py cuttlefish-12.08/cuttlefish/plugins/__init__.py --- cuttlefish-12.07.13/cuttlefish/plugins/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/__init__.py 2012-08-25 10:22:17.000000000 +0000 @@ -0,0 +1,198 @@ +import os +import os.path +import sys +import imp +import inspect +import datetime +from traceback import format_exception +import logging + +_mod_logger = logging.getLogger('cuttlefish.plugins') +_engine_logger = logging.getLogger('cuttlefish.engine.Engine') + + +class CuttlePlugin: + NAME = "UNKOWN" + DESCP = "No documnentation passed with this plugin." + CATEGORY = "Unkown" + PARAMS = {} + + def __init__(self): + self._params = {} + self.set_params(self.PARAMS) + + def get_params(self): + return self._params + + def set_params(self, params): + self._params.update(params) + + + class Editor: + def set_params(self, params): + self._params = params + + def get_params(self): + return self._params + + + def begin(self): + return {} + + def finish(self, ui): + pass + + +class Manager: + def __init__(self): + self._dirs = [] + self._plugins = {} + + self.__add_dir('cuttlefish.plugins.user', '~/.cuttlefish/plugins') + self.__add_dir('cuttlefish.plugins.default', '/opt/extras.ubuntu.com/cuttlefish/cuttlefish/plugins') + self.__add_dir('cuttlefish.plugins.altdefault', '/usr/lib/python2.7/dist-packages/cuttlefish/plugins') + + def __add_dir(self, modid, dir): + dir = os.path.abspath(os.path.expandvars(os.path.expanduser(dir))) + if os.path.isdir(dir): + self._dirs.append((modid, dir)) + + + def is_plugin(self, path): + fname = os.path.basename(path) + if fname.endswith('.py'): + for modid, dir in self._dirs: + if path.startswith(dir): + return True + return False + + def reload(self): + self._plugins = {} + + for modid, dir in self._dirs: + for mod_file in [f for f in os.listdir(dir) if (not f.startswith('__')) and f.endswith('.py')]: + module = mod_file[:-3] + f = None + try: + f, path, desc = imp.find_module(module, [dir]) + mod = imp.load_module(modid + '_' + module, f, path, desc) + for cname, clazz in inspect.getmembers(mod, inspect.isclass): + if not clazz is CuttlePlugin and CuttlePlugin in inspect.getmro(clazz) and cname not in self._plugins: + self._plugins[cname] = clazz + _mod_logger.info("Loaded plugin '%s' from '%s'" % (cname, os.path.join(dir, mod_file))) + finally: + if f: + f.close() + + def list(self, parent): + for cname, clazz in self._plugins.items(): + if parent in inspect.getmro(clazz): + yield (cname, clazz) + + + def get_class(self, parent, id, fallback): + result = fallback + if id in self._plugins and parent in inspect.getmro(self._plugins[id]): + result = self._plugins[id] + else: + id = 'UNKOWN' + result.logger = logging.getLogger('cuttlefish.plugins.%s' % id) + return result + + +MANAGER = Manager() + + + +def debug(msg): + _mod_logger.warning('THE DEBUG FUNCTION IS DEPRECATED. Please use self.logger.debug()') + _mod_logger.debug(msg) + +def format_plugin_exc(exc_info = None): + if not exc_info: + exc_info = sys.exc_info() + return "%s\n%s\n" % (exc_info[1], "".join(format_exception(*exc_info))) + +_parentExcepthook = sys.excepthook +def _handle_exc(type, value, traceback): + # Some exceptions in plugins are not catched this way... so I allow + # all exceptions to be logged + #tb = traceback + #while tb.tb_next: + # tb = tb.tb_next + #if MANAGER.is_plugin(inspect.getframeinfo(tb).filename): + _engine_logger.error(format_plugin_exc((type, value, traceback))) + _parentExcepthook(type, value, traceback) + +sys.excepthook = _handle_exc + +EXAMPLE_CODE = """from cuttlefish.actions import CuttleAction +from cuttlefish.events import CuttleEvent +from cuttlefish.plugins import CuttlePlugin +from cuttlefish.params import StringParam, FileParam + +from gi.repository import GObject +import os.path + + +class HelloWorldWatcher(CuttleEvent, CuttlePlugin): + NAME = "Hello World Watcher" + DESCP = "Checks for ~/HelloWorld.txt" + CATEGORY = "Your Plugins" + + def __init__(self): + CuttleEvent.__init__(self) + CuttlePlugin.__init__(self) + + + def _isCreated(self): + return os.path.isfile(os.path.join(os.path.expanduser('~'), 'HelloWorld.txt')) + + def setup(self): + self._created = self._isCreated() + self._hid = GObject.timeout_add(1000, self._check) + + + def teardown(self): + GObject.source_remove(self._hid) + + def _check(self): + created = self._isCreated() + if not self._created and created: + self.logger.debug('Just testing the debug functionality (this is written to /tmp/cuttlefish.log)') + self.trigger() + self._created = created + + return True + + +class HelloWorldWriter(CuttleAction, CuttlePlugin): + NAME = "Hello World Writer" + DESCP = "Writes something to ~/HelloWorld.txt" + CATEGORY = "Your Plugins" + PARAMS = { + 'msg' : 'Hello World :-)', + 'file' : os.path.join(os.path.expanduser('~'), 'HelloWorld.txt') + } + + class Editor(CuttlePlugin.Editor): + ORDER = ['msg', 'file'] + + def begin(self): + return { + 'msg' : StringParam('Your message'), + 'file': FileParam('The File') + } + + + + def __init__(self): + CuttleAction.__init__(self) + CuttlePlugin.__init__(self) + + + def execute(self): + with open(self._params['file'], 'w') as hwf: + hwf.write(self._params['msg']) + +""" diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/audio.py cuttlefish-12.08/cuttlefish/plugins/audio.py --- cuttlefish-12.07.13/cuttlefish/plugins/audio.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/audio.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,4 +1,5 @@ from cuttlefish.actions import CuttleAction +from cuttlefish.plugins import CuttlePlugin, debug from cuttlefish.params import IntParam from gi.repository import Gtk @@ -7,32 +8,70 @@ import signal -class SetVolume(CuttleAction): - - def __init__(self): +class PACtrlAction(CuttleAction): + CATEGORY = 'Audio' + + def __init__(self, paramCallback): CuttleAction.__init__(self) - self._pid = None + self._paramCallback = paramCallback + + def execute(self): + argv = ['pactl'] + + _env = os.environ.copy() + _env['LC_ALL'] = 'C' + p = subprocess.Popen(['pactl', 'stat'], stdout=subprocess.PIPE, env=_env) + out, err = p.communicate() + for key, value in [x.split(': ') for x in out[:-1].split('\n')]: + if key == 'Default Sink': + default_sink = value + break + + idx = '0' + p = subprocess.Popen(['pactl', 'list', 'short', 'sinks'], stdout=subprocess.PIPE, env=_env) + out, err = p.communicate() + for sink_info in [x.split('\t') for x in out[:-1].split('\n')]: + if sink_info[1] == default_sink: + idx = sink_info[0] + break - @classmethod - def get_name(clazz): - return 'Audio :: Set Volume' - - @classmethod - def get_descp(clazz): - return 'Set the volume' - @classmethod - def get_default_params(clazz): - return { - 'volume': '50' - } + argv.extend(self._paramCallback(idx)) + subprocess.Popen(argv) + + +class SetVolume(PACtrlAction, CuttlePlugin): + NAME = 'Change volume' + DESCP = 'Change the global volume' + PARAMS = { + 'volume': 50 + } + + class Editor(CuttlePlugin.Editor): + def begin(self): + return { + 'volume': IntParam('Volume (in %)', Gtk.Adjustment(0, 0, 100, 1, 0, 0)) + } - @classmethod - def get_param_ui(clazz): - return { - 'volume': IntParam('Volume (in %)', Gtk.Adjustment(0, 0, 100, 1, 0, 0)) - } + def __init__(self): + PACtrlAction.__init__(self, lambda idx : ['set-sink-volume', idx, str(int(self._params['volume'])) + '%']) + CuttlePlugin.__init__(self) + + +class MuteVolume(PACtrlAction, CuttlePlugin): + NAME = 'Mute volume' + DESCP = 'Mute the global volume' - def execute(self): - argv = ['pactl', 'set-sink-volume', '0', str(int(self._params['volume'])) + '%'] - subprocess.Popen(argv) + def __init__(self): + PACtrlAction.__init__(self, lambda idx : ['set-sink-mute', idx, '1']) + CuttlePlugin.__init__(self) + + +class UnmuteVolume(PACtrlAction, CuttlePlugin): + NAME = 'Unmute volume' + DESCP = 'Unmute the global volume' + + def __init__(self): + PACtrlAction.__init__(self, lambda idx : ['set-sink-mute', idx, '0']) + CuttlePlugin.__init__(self) + diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/bluez.py cuttlefish-12.08/cuttlefish/plugins/bluez.py --- cuttlefish-12.07.13/cuttlefish/plugins/bluez.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/bluez.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,237 +1,127 @@ from cuttlefish.actions import CuttleAction -from cuttlefish.events import CuttleEvent +from cuttlefish.events import CuttleEvent, DBusEvent +from cuttlefish.plugins import CuttlePlugin from cuttlefish.params import SelectParam, FileParam import dbus -class TurnOnBluez(CuttleAction): - - @classmethod - def get_name(clazz): - return 'Bluetooth :: Activate' - - @classmethod - def get_descp(clazz): - return 'Turn on bluetooth' +def _get_default_adapter_path(bus): + bluezobj = bus.get_object('org.bluez', '/') + bluezmgr = dbus.Interface(bluezobj, 'org.bluez.Manager') - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def execute(self): - bus = dbus.SystemBus() - bluezobj = bus.get_object('org.bluez', '/') - bluezmgr = dbus.Interface(bluezobj, 'org.bluez.Manager') - - adapobj = bus.get_object('org.bluez', bluezmgr.DefaultAdapter()) - adapter = dbus.Interface(adapobj, 'org.bluez.Adapter') - adapter.SetProperty('Powered', True) + return bluezmgr.DefaultAdapter() +class ChangeBluezPower(CuttleAction): + CATEGORY = "Bluetooth" -class TurnOffBluez(CuttleAction): + def __init__(self, powered): + CuttleAction.__init__(self) + self._powered = powered - @classmethod - def get_name(clazz): - return 'Bluetooth :: Deactivate' - - @classmethod - def get_descp(clazz): - return 'Turn off bluetooth' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} def execute(self): - bus = dbus.SystemBus() - bluezobj = bus.get_object('org.bluez', '/') - bluezmgr = dbus.Interface(bluezobj, 'org.bluez.Manager') - adapobj = bus.get_object('org.bluez', bluezmgr.DefaultAdapter()) + bus = dbus.SystemBus() + adapobj = bus.get_object('org.bluez', _get_default_adapter_path(bus)) adapter = dbus.Interface(adapobj, 'org.bluez.Adapter') - adapter.SetProperty('Powered', False) + adapter.SetProperty('Powered', self._powered) +class TurnOnBluez(ChangeBluezPower, CuttlePlugin): + NAME = "Activate Bluetooth" + DESCP = "Turn on the default bluetooth adapter" + def __init__(self): + ChangeBluezPower.__init__(self, True) +class TurnOffBluez(ChangeBluezPower, CuttlePlugin): + NAME = "Deactivate Bluetooth" + DESCP = "Turn off the default bluetooth adapter" + def __init__(self): + ChangeBluezPower.__init__(self, False) -class BluezActivated(CuttleEvent): - def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SystemBus() +class BluezPowerChanged(DBusEvent): + CATEGORY = "Bluetooth" - @classmethod - def get_name(clazz): - return "Bluetooth turned on" - - @classmethod - def get_descp(clazz): - return "React when bluetooth is turned on" - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def setup(self): - bluezobj = self._bus.get_object('org.bluez', '/') - bluezmgr = dbus.Interface(bluezobj, 'org.bluez.Manager') - - self._hid = self._bus.add_signal_receiver(self.on_prop_changed, signal_name="PropertyChanged", dbus_interface="org.bluez.Adapter", path=bluezmgr.DefaultAdapter()) - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) + def __init__(self, powered): + DBusEvent.__init__(self, self.on_prop_changed, 'PropertyChanged', 'org.bluez.Adapter', lambda : _get_default_adapter_path(self._bus), DBusEvent.USE_SYSTEM_BUS) + CuttlePlugin.__init__(self) + self._powered = powered def on_prop_changed(self, prop, value): - if prop == 'Powered' and value == True: + if prop == 'Powered' and value == self._powered: self.trigger() +class BluezActivated(BluezPowerChanged, CuttlePlugin): + NAME = "Bluetooth activated" + DESCP = "React when bluetooth is turned on" + + def __init__(self): + BluezPowerChanged.__init__(self, True) + CuttlePlugin.__init__(self) + -class BluezDeactivated(CuttleEvent): +class BluezDeactivated(BluezPowerChanged, CuttlePlugin): + NAME = "Bluetooth deactivated" + DESCP = "React when bluetooth is turned off" def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SystemBus() + BluezPowerChanged.__init__(self, False) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return "Bluetooth turned off" - - @classmethod - def get_descp(clazz): - return "React when bluetooth is turned off" - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def setup(self): - bluezobj = self._bus.get_object('org.bluez', '/') - bluezmgr = dbus.Interface(bluezobj, 'org.bluez.Manager') - - self._hid = self._bus.add_signal_receiver(self.on_prop_changed, signal_name="PropertyChanged", dbus_interface="org.bluez.Adapter", path=bluezmgr.DefaultAdapter()) - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) - - def on_prop_changed(self, prop, value): - if prop == 'Powered' and value == False: - self.trigger() +class BluezDeviceChanged(DBusEvent): + CATEGORY = "Bluetooth" + PARAMS = {'dev' : ''} -class BluezDeviceDisappear(CuttleEvent): + class Editor(CuttlePlugin.Editor): + def begin(self): + bus = dbus.SystemBus() + adapobj = bus.get_object('org.bluez', _get_default_adapter_path(bus)) + adapter = dbus.Interface(adapobj, 'org.bluez.Adapter') - def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SystemBus() + devs = {} + for devpath in adapter.ListDevices(): + devobj = bus.get_object('org.bluez', devpath) + device = dbus.Interface(devobj, 'org.bluez.Device') + props = device.GetProperties() - @classmethod - def get_name(clazz): - return "Bluetooth device disconnects" - - @classmethod - def get_descp(clazz): - return "React when a specified bluetooth device disconnects" - - @classmethod - def get_default_params(clazz): - return {'dev' : ''} - - @classmethod - def get_param_ui(clazz): - bus = dbus.SystemBus() - bluezobj = bus.get_object('org.bluez', '/') - bluezmgr = dbus.Interface(bluezobj, 'org.bluez.Manager') - adapobj = bus.get_object('org.bluez', bluezmgr.DefaultAdapter()) - adapter = dbus.Interface(adapobj, 'org.bluez.Adapter') + devs[str(devpath)] = props['Name'] + + + return {'dev' : SelectParam('Monitored device', devs, str)} - devs = {} - for devpath in adapter.ListDevices(): - devobj = bus.get_object('org.bluez', devpath) - device = dbus.Interface(devobj, 'org.bluez.Device') - props = device.GetProperties() - devs[str(devpath)] = props['Name'] - - - return {'dev' : SelectParam('Monitored device', devs, str)} - - def setup(self): - self._hid = self._bus.add_signal_receiver(self.on_prop_changed, signal_name="PropertyChanged", dbus_interface="org.bluez.Device", path=self._params['dev']) - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) + + def __init__(self, connected): + DBusEvent.__init__(self, self.on_prop_changed, 'PropertyChanged', 'org.bluez.Device', lambda : self._params['dev'], DBusEvent.USE_SYSTEM_BUS) + CuttlePlugin.__init__(self) + self._connected = connected def on_prop_changed(self, prop, value): - if prop == 'Connected' and value == False: + if prop == 'Connected' and value == self._connected: self.trigger() - -class BluezDeviceFound(CuttleEvent): +class BluezDeviceDisappear(BluezDeviceChanged, CuttlePlugin): + NAME = "Bluetooth device disconnects" + DESCP ="React when a specified bluetooth device disconnects" def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SystemBus() + BluezDeviceChanged.__init__(self, False) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return "Bluetooth device connects" - - @classmethod - def get_descp(clazz): - return "React when a specified bluetooth device connects" - - @classmethod - def get_default_params(clazz): - return {'dev' : ''} - - @classmethod - def get_param_ui(clazz): - bus = dbus.SystemBus() - bluezobj = bus.get_object('org.bluez', '/') - bluezmgr = dbus.Interface(bluezobj, 'org.bluez.Manager') - adapobj = bus.get_object('org.bluez', bluezmgr.DefaultAdapter()) - adapter = dbus.Interface(adapobj, 'org.bluez.Adapter') - devs = {} - for devpath in adapter.ListDevices(): - devobj = bus.get_object('org.bluez', devpath) - device = dbus.Interface(devobj, 'org.bluez.Device') - props = device.GetProperties() +class BluezDeviceFound(BluezDeviceChanged, CuttlePlugin): + NAME = "Bluetooth device connects" + DESCP ="React when a specified bluetooth device connects" - devs[str(devpath)] = props['Name'] - - - return {'dev' : SelectParam('Monitored device', devs, str)} - - def setup(self): - self._hid = self._bus.add_signal_receiver(self.on_prop_changed, signal_name="PropertyChanged", dbus_interface="org.bluez.Device", path=self._params['dev']) - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) - - def on_prop_changed(self, prop, value): - if prop == 'Connected' and value == True: - self.trigger() + def __init__(self): + BluezDeviceChanged.__init__(self, True) + CuttlePlugin.__init__(self) \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/date.py cuttlefish-12.08/cuttlefish/plugins/date.py --- cuttlefish-12.07.13/cuttlefish/plugins/date.py 1970-01-01 00:00:00.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/date.py 2012-08-25 10:22:17.000000000 +0000 @@ -0,0 +1,50 @@ +from cuttlefish.plugins import CuttlePlugin +from cuttlefish.events import CuttleEvent +from cuttlefish.params import MultiSelectParam, TimeParam +from gi.repository import GObject +from datetime import datetime + + + +class TimeEvent(CuttleEvent, CuttlePlugin): + NAME = 'At time of day' + DESCP = "React at a specific time of the day" + CATEGORY = 'Date & Time' + PARAMS = { + 'time' : (15,30), + 'days' : [0,1,2,3,4,5,6] + } + + class Editor(CuttlePlugin.Editor): + ORDER = ['time', 'days'] + + def begin(self): + return { + 'time' : TimeParam('Time'), + 'days' : MultiSelectParam('Days', { + 0 : 'Monday', + 1 : 'Tuesday', + 2 : 'Wednesday', + 3 : 'Thursdays', + 4 : 'Friday', + 5 : 'Saturday', + 6 : 'Sunday' + }) + } + + def __init__(self): + CuttleEvent.__init__(self) + CuttlePlugin.__init__(self) + + def setup(self): + self._hid = GObject.timeout_add(60000, self._check) + + def teardown(self): + GObject.source_remove(self._hid) + + def _check(self): + now = datetime.now() + if (now.hour, now.minute) == self._params['time'] and now.weekday() in self._params['days']: + self.trigger() + + return True \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/internals.py cuttlefish-12.08/cuttlefish/plugins/internals.py --- cuttlefish-12.07.13/cuttlefish/plugins/internals.py 1970-01-01 00:00:00.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/internals.py 2012-08-25 10:22:17.000000000 +0000 @@ -0,0 +1,96 @@ +from cuttlefish.actions import CuttleAction +from cuttlefish.plugins import CuttlePlugin +from cuttlefish.events import CuttleEvent +from cuttlefish.params import SelectParam + +from cuttlefish.reflexes import REFLEXES +from cuttlefish.engine import ENGINE + +class ChangeReflex(CuttleAction): + CATEGORY = "Cuttlefish Internals" + PARAMS = { + 'reflex' : '' + } + + class Editor(CuttlePlugin.Editor): + def begin(self): + rdict = {} + for r in REFLEXES.list(): + rdict[r.record_id] = r['name'] + + return { + 'reflex': SelectParam('Reflex', rdict, str) + } + + + def __init__(self, activate): + CuttleAction.__init__(self) + self._activate = activate + + def execute(self): + r = REFLEXES.get(self._params['reflex']) + r['event']['isActive'] = self._activate + REFLEXES.save(r) + + + +class ActivateReflex(ChangeReflex, CuttlePlugin): + NAME = "Activate reflex" + DESCP = "Activate another reflex" + + def __init__(self): + ChangeReflex.__init__(self, True) + CuttlePlugin.__init__(self) + + +class DeactivateReflex(ChangeReflex, CuttlePlugin): + NAME = "Deactivate reflex" + DESCP = "Deactivate another reflex" + + def __init__(self): + ChangeReflex.__init__(self, False) + CuttlePlugin.__init__(self) + + + +class CuttleEngineMonitor(CuttleEvent): + CATEGORY = "Cuttlefish Internals" + + def __init__(self, event): + CuttleEvent.__init__(self) + self._event = event + + def setup(self): + self._hid = ENGINE.connect(self._event, self.on_engine_event) + + def teardown(self): + ENGINE.disconnect(self._hid) + + def on_engine_event(self, engine): + pass + + +class CuttleEngineStartup(CuttleEngineMonitor, CuttlePlugin): + NAME = "Cuttlefish Startup" + DESCP = "React when cuttlefish is started" + + + def __init__(self): + CuttleEngineMonitor.__init__(self, 'startup') + CuttlePlugin.__init__(self) + + def on_engine_event(self, engine): + self.trigger() + + +class CuttleEngineShutdown(CuttleEngineMonitor, CuttlePlugin): + NAME = "Cuttlefish Shutdown" + DESCP = "React when cuttlefish is stopped" + + + def __init__(self): + CuttleEngineMonitor.__init__(self, 'shutdown') + CuttlePlugin.__init__(self) + + def on_engine_event(self, engine): + self.trigger() diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/other.py cuttlefish-12.08/cuttlefish/plugins/other.py --- cuttlefish-12.07.13/cuttlefish/plugins/other.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/other.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,31 +1,28 @@ from cuttlefish.actions import CuttleAction +from cuttlefish.plugins import CuttlePlugin from cuttlefish.params import IntParam, SelectParam, FileParam from threading import Event from gi.repository import Gio, Gtk from subprocess import Popen, PIPE -class Wait(CuttleAction): - +class Wait(CuttleAction, CuttlePlugin): + NAME = "Wait" + DESCP = "Wait for a specific amount of time" + CATEGORY = "Misc" + PARAMS = { + 'time' : 10 + } + + class Editor(CuttlePlugin.Editor): + def begin(self): + return {'time': IntParam('Time to wait (in seconds)')} + + def __init__(self): CuttleAction.__init__(self) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return 'Other :: Wait' - - @classmethod - def get_descp(clazz): - return 'Wait for a specific amount of time' - - @classmethod - def get_default_params(clazz): - return {'time': '10'} - - @classmethod - def get_param_ui(clazz): - return {'time': IntParam('Time to wait (in seconds)')} - def execute(self): self._event = Event() self._event.wait(int(self._params['time'])) @@ -35,30 +32,27 @@ -class ProxyMode(CuttleAction): - +class ProxyMode(CuttleAction, CuttlePlugin): + NAME = "Change proxy mode" + DESCP = "Chagne proxy mode" + CATEGORY = "Network" + PARAMS = { + 'mode': 'none' + } + + class Editor(CuttlePlugin.Editor): + + def begin(self): + return {'mode': SelectParam('Proxy Mode', { + 'none': 'None', + 'manual': 'Manual Configuration', + 'auto': 'Automatic Configuration' + }, str)} + + def __init__(self): CuttleAction.__init__(self) - - @classmethod - def get_name(clazz): - return 'Other :: Change proxy mode' - - @classmethod - def get_descp(clazz): - return 'Change the proxy mode' - - @classmethod - def get_default_params(clazz): - return {'mode': 'none'} - - @classmethod - def get_param_ui(clazz): - return {'mode': SelectParam('Proxy Mode', { - 'none': 'None', - 'manual': 'Manual Configuration', - 'auto': 'Automatic Configuration' - }, str)} + CuttlePlugin.__init__(self) def execute(self): settings = Gio.Settings.new('org.gnome.system.proxy') @@ -66,83 +60,73 @@ -class DefaultPrinter(CuttleAction): - +class DefaultPrinter(CuttleAction, CuttlePlugin): + NAME = 'Change default printer' + DESCP = 'Change the default printer' + CATEGORY = 'Hardware' + PARAMS = { + 'printer': '' + } + + class Editor(CuttlePlugin.Editor): + def begin(self): + p = Popen(['lpstat', '-a'], stdout=PIPE) + out, err = p.communicate() + printers = {} + for printer in [x.split(' ', 1)[0] for x in out[:-1].split('\n')]: + printers[printer] = printer + + return {'printer': SelectParam('Printer', printers, str)} + def __init__(self): CuttleAction.__init__(self) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return 'Other :: Change default printer' - - @classmethod - def get_descp(clazz): - return 'Change the default printer' - - @classmethod - def get_default_params(clazz): - return {'printer': ''} - - @classmethod - def get_param_ui(clazz): - p = Popen(['lpstat', '-a'], stdout=PIPE) - out, err = p.communicate() - printers = {} - for printer in map(lambda x : x.split(' ', 1)[0], out[:-1].split('\n')): - printers[printer] = printer - - return {'printer': SelectParam('Printer', printers, str)} - def execute(self): Popen(['lpoptions', '-d', self._params['printer']], stdout=PIPE).communicate() -class BackgroundChanger(CuttleAction): +class BackgroundChanger(CuttleAction, CuttlePlugin): + NAME = "Change desktop background" + DESCP = "Change the background wallpaper of the desktop" + CATEGORY = "Misc" + PARAMS = { + 'file': '', + 'mode': 'streched' + } + + class Editor(CuttlePlugin.Editor): + ORDER = ['file', 'mode'] + def begin(self): + f_img = Gtk.FileFilter() + f_img.set_name("Images") + f_img.add_mime_type("image/png") + f_img.add_mime_type("image/jpeg") + f_img.add_mime_type("image/gif") + f_img.add_pattern("*.png") + f_img.add_pattern("*.jpg") + f_img.add_pattern("*.gif") + f_img.add_pattern("*.tif") + f_img.add_pattern("*.xpm") + + return { + 'file': FileParam('Image', [f_img]), + 'mode': SelectParam('Display Mode', { + 'wallpaper': 'Tile', + 'zoom': 'Zoom', + 'centered': 'Center', + 'scaled': 'Scale', + 'streched': 'Fill', + 'spanned': 'Span' + }, str)} + def __init__(self): CuttleAction.__init__(self) - - @classmethod - def get_name(clazz): - return 'Other :: Change desktop background' - - @classmethod - def get_descp(clazz): - return 'Change the background of the desktop' - - @classmethod - def get_default_params(clazz): - return { - 'file': '', - 'mode': 'streched' - } - - @classmethod - def get_param_ui(clazz): - f_img = Gtk.FileFilter() - f_img.set_name("Images") - f_img.add_mime_type("image/png") - f_img.add_mime_type("image/jpeg") - f_img.add_mime_type("image/gif") - f_img.add_pattern("*.png") - f_img.add_pattern("*.jpg") - f_img.add_pattern("*.gif") - f_img.add_pattern("*.tif") - f_img.add_pattern("*.xpm") - - return { - 'file': FileParam('Image', [f_img]), - 'mode': SelectParam('Display Mode', { - 'wallpaper': 'Tile', - 'zoom': 'Zoom', - 'centered': 'Center', - 'scaled': 'Scale', - 'streched': 'Fill', - 'spanned': 'Span' - }, str)} - + CuttlePlugin.__init__(self) + def execute(self): settings = Gio.Settings.new('org.gnome.desktop.background') settings.set_string('picture-options', self._params['mode']) - settings.set_string('picture-uri', 'file://' + self._params['file']) \ No newline at end of file + settings.set_string('picture-uri', 'file://' + self._params['file']) diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/pidgin.py cuttlefish-12.08/cuttlefish/plugins/pidgin.py --- cuttlefish-12.07.13/cuttlefish/plugins/pidgin.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/pidgin.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,34 +1,34 @@ from cuttlefish.actions import CuttleAction +from cuttlefish.plugins import CuttlePlugin from cuttlefish.params import SelectParam import dbus -class ChangePidginStatus(CuttleAction): - - @classmethod - def get_name(clazz): - return 'Pidgin :: Change status' - - @classmethod - def get_descp(clazz): - return 'Activate a saved status of pidgin' +class ChangePidginStatus(CuttleAction, CuttlePlugin): + NAME = 'Change pidgin status' + DESCP = 'Activate a saved status of pidgin' + CATEGORY = 'Pidgin' + PARAMS = { + 'statusid' : -1 + } - @classmethod - def get_default_params(clazz): - return {'statusid' : -1} - - @classmethod - def get_param_ui(clazz): - bus = dbus.SessionBus(); - pidgobj = bus.get_object('im.pidgin.purple.PurpleService', '/im/pidgin/purple/PurpleObject') - pidgin = dbus.Interface(pidgobj, 'im.pidgin.purple.PurpleInterface') + class Editor(CuttlePlugin.Editor): + def begin(self): + bus = dbus.SessionBus(); + pidgobj = bus.get_object('im.pidgin.purple.PurpleService', '/im/pidgin/purple/PurpleObject') + pidgin = dbus.Interface(pidgobj, 'im.pidgin.purple.PurpleInterface') + + stats = {} + for sid in pidgin.PurpleSavedstatusesGetAll(): + stats[sid] = pidgin.PurpleSavedstatusGetTitle(sid) - stats = {} - for sid in pidgin.PurpleSavedstatusesGetAll(): - stats[sid] = pidgin.PurpleSavedstatusGetTitle(sid) + return {'statusid' : SelectParam('Select Status', stats)} + - return {'statusid' : SelectParam('Select Status', stats)} - + def __init__(self): + CuttleAction.__init__(self) + CuttlePlugin.__init__(self) + def execute(self): bus = dbus.SessionBus(); pidgobj = bus.get_object('im.pidgin.purple.PurpleService', '/im/pidgin/purple/PurpleObject') diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/power.py cuttlefish-12.08/cuttlefish/plugins/power.py --- cuttlefish-12.07.13/cuttlefish/plugins/power.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/power.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,26 +1,19 @@ from cuttlefish.actions import CuttleAction -from cuttlefish.events import CuttleEvent +from cuttlefish.plugins import CuttlePlugin +from cuttlefish.events import DBusEvent +from cuttlefish.params import SelectParam import dbus -class Suspend(CuttleAction): +class Suspend(CuttleAction, CuttlePlugin): + NAME = 'Suspend' + DESCP = 'Suspend the computer' + CATEGORY = 'Power Management' + + def __init__(self): + CuttleAction.__init__(self) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return 'Power :: Suspend' - - @classmethod - def get_descp(clazz): - return 'Suspend the computer' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - def execute(self): bus = dbus.SystemBus() obj = bus.get_object('org.freedesktop.UPower', '/org/freedesktop/UPower') @@ -29,23 +22,14 @@ upower.Suspend() -class Hibernate(CuttleAction): - - @classmethod - def get_name(clazz): - return 'Power :: Hibernate' - - @classmethod - def get_descp(clazz): - return 'Hibernate the computer' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} +class Hibernate(CuttleAction, CuttlePlugin): + NAME = 'Hibernate' + DESCP = 'Hibernate the computer' + CATEGORY = 'Power Management' + + def __init__(self): + CuttleAction.__init__(self) + CuttlePlugin.__init__(self) def execute(self): bus = dbus.SystemBus() @@ -55,24 +39,15 @@ upower.Hibernate() -class Shutdown(CuttleAction): +class Shutdown(CuttleAction, CuttlePlugin): + NAME = 'Shutdown' + DESCP = 'Shut down the computer' + CATEGORY = 'Power Management' + + def __init__(self): + CuttleAction.__init__(self) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return 'Power :: Shutdown' - - @classmethod - def get_descp(clazz): - return 'Shut down the computer' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - def execute(self): bus = dbus.SystemBus() obj = bus.get_object('org.freedesktop.ConsoleKit', '/org/freedesktop/ConsoleKit/Manager') @@ -81,26 +56,11 @@ consoleKit.Stop() +class Reboot(CuttleAction, CuttlePlugin): + NAME = 'Reboot' + DESCP = 'Reboot the computer' + CATEGORY = 'Power Management' - -class Reboot(CuttleAction): - - @classmethod - def get_name(clazz): - return 'Power :: Reboot' - - @classmethod - def get_descp(clazz): - return 'Reboot the computer' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - def execute(self): bus = dbus.SystemBus() obj = bus.get_object('org.freedesktop.ConsoleKit', '/org/freedesktop/ConsoleKit/Manager') @@ -109,3 +69,32 @@ consoleKit.Restart() +class WakeupMonitor(DBusEvent, CuttlePlugin): + NAME = 'Wake Up' + DESCP = 'React when the computer wakes up from suspend or hibernate' + PARAMS = {'type' : 2} + CATEGORY = 'Power Management' + + + class Editor(CuttlePlugin.Editor): + def begin(self): + return {'type' : SelectParam('Kind of wakeup', { + 0 : 'Wake from suspend only', + 1 : 'Wake from hibernate only', + 2 : 'Wake from suspend or hibernate' + })} + + + def __init__(self): + DBusEvent.__init__(self, self.on_wakeup, 'NotifyResume', 'org.freedesktop.UPower', '/org/freedesktop/UPower', DBusEvent.USE_SYSTEM_BUS) + CuttlePlugin.__init__(self) + + def on_wakeup(self, wakeUpType): + types = [ + 'suspend', + 'hibernate', + wakeUpType + ] + + if wakeUpType == types[self._params['type']]: + self.trigger() \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/reflexes.py cuttlefish-12.08/cuttlefish/plugins/reflexes.py --- cuttlefish-12.07.13/cuttlefish/plugins/reflexes.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/reflexes.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,69 +0,0 @@ -from cuttlefish.actions import CuttleAction -from cuttlefish.params import SelectParam - -from cuttlefish.reflexes import REFLEXES - -class ActivateReflex(CuttleAction): - - @classmethod - def get_name(clazz): - return 'Reflex :: Activate' - - @classmethod - def get_descp(clazz): - return 'Activates a reflex' - - @classmethod - def get_default_params(clazz): - return { - 'reflex': '', - } - - @classmethod - def get_param_ui(clazz): - bdict = {} - for b in REFLEXES.list(): - bdict[b.record_id] = b['name'] - - return { - 'reflex': SelectParam('Reflex', bdict, str) - } - - def execute(self): - b = REFLEXES.get(self._params['reflex']) - b['event']['isActive'] = True - REFLEXES.save(b) - - - - -class DeactivateReflex(CuttleAction): - - @classmethod - def get_name(clazz): - return 'Reflex :: Deactivate' - - @classmethod - def get_descp(clazz): - return 'Deactivates a reflex' - - @classmethod - def get_default_params(clazz): - return { - 'reflex': '', - } - - @classmethod - def get_param_ui(clazz): - bdict = {} - for b in REFLEXES.list(): - bdict[b.record_id] = b['name'] - - return { - 'reflex': SelectParam('Reflex', bdict, str) - } - - def execute(self): - b = REFLEXES.get(self._params['reflex']) - b['event']['isActive'] = False - REFLEXES.save(b) diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/run.py cuttlefish-12.08/cuttlefish/plugins/run.py --- cuttlefish-12.07.13/cuttlefish/plugins/run.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/run.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,131 +1,122 @@ from cuttlefish.actions import CuttleAction from cuttlefish.events import CuttleEvent +from cuttlefish.plugins import CuttlePlugin from cuttlefish.params import StringParam, BoolParam, SelectAppParam, FolderParam, FileParam -from gi.repository import Wnck +from gi.repository import Wnck, Gio import subprocess import os +import os.path import signal +import psutil +class RunApplication(CuttleAction, CuttlePlugin): + NAME = 'Start application' + DESCP = 'Starts an application' + CATEGORY = 'Applications' + PARAMS = { + 'path' : 'firefox', + 'wait' : False + } + + class Editor(CuttlePlugin.Editor): + ORDER = ['path', 'wait'] + + def begin(self): + return { + 'path': SelectAppParam('Application'), + 'wait': BoolParam('Wait until application exits') + } -class RunApplication(CuttleAction): - def __init__(self): CuttleAction.__init__(self) - self._pid = None - - @classmethod - def get_name(clazz): - return 'Application :: Start' - - @classmethod - def get_descp(clazz): - return 'Run a application' - - @classmethod - def get_default_params(clazz): - return { - 'path': 'firefox', - 'wait' : False - } - - @classmethod - def get_param_ui(clazz): - return { - 'path': SelectAppParam('Application'), - 'wait': BoolParam('Wait until application exits') - } + CuttlePlugin.__init__(self) + self._proc = None def execute(self): argv = self._params['path'].split(' ') - p = subprocess.Popen(argv) - self._pid = p.pid + self._proc = subprocess.Popen(argv) if self._params['wait']: - p.wait() + self._proc.wait() def interrupt(self): if self._params['wait']: - os.kill(self._pid, signal.SIGKILL) + self._proc.kill() -class RunApplicationEx(CuttleAction): - - def __init__(self): - CuttleAction.__init__(self) - self._pid = None +class RunApplicationEx(CuttleAction, CuttlePlugin): + NAME = 'Start application (advanced mode)' + DESCP = 'Starts any executable application with extra parameters' + CATEGORY = 'Applications' + PARAMS = { + 'path': '/usr/bin/firefox', + 'params': '', + 'wdir': '', + 'term': False, + 'wait' : False + } - @classmethod - def get_name(clazz): - return 'Application :: Start (advanced mode)' - - @classmethod - def get_descp(clazz): - return 'Run any executable application with advanced options' - - @classmethod - def get_default_params(clazz): - return { - 'path': '/usr/bin/firefox', - 'params': '', - 'wdir': '', - 'wait' : False - } - - @classmethod - def get_param_ui(clazz): - return { + class Editor(CuttlePlugin.Editor): + ORDER = ['path', 'params', 'wdir', 'term', 'wait'] + + def begin(self): + return { 'path': FileParam('Executable'), 'params': StringParam('Parameters'), 'wdir': FolderParam('Working directory'), + 'term' : BoolParam('Run in terminal'), 'wait': BoolParam('Wait until application exits') } + + def __init__(self): + CuttleAction.__init__(self) + CuttlePlugin.__init__(self) + self._proc = None def execute(self): argv = (self._params['path'] + ' ' + self._params['params']).split(' ') + if self._params['term']: + settings = Gio.Settings("org.gnome.desktop.default-applications.terminal") + argv.insert(0, settings.get_string('exec')) + execArg = settings.get_string('exec-arg') + if execArg: + argv.insert(1, execArg) + wdir = self._params['wdir'] if wdir == '': wdir = None - p = subprocess.Popen(argv,cwd=wdir) - self._pid = p.pid + self._proc = subprocess.Popen(argv,cwd=wdir) if self._params['wait']: - p.wait() + self._proc.wait() def interrupt(self): if self._params['wait']: - os.kill(self._pid, signal.SIGKILL) - - + self._proc.kill() + -class KillApplication(CuttleAction): - +class KillApplication(CuttleAction, CuttlePlugin): + NAME = 'Stop an application' + DESCP = 'Kill all instances of an application' + CATEGORY = 'Applications' + PARAMS = { + 'name' : 'firefox' + } + + class Editor(CuttlePlugin.Editor): + def begin(self): + return { + 'name': SelectAppParam('Application') + } + def __init__(self): CuttleAction.__init__(self) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return 'Application :: Stop' - - @classmethod - def get_descp(clazz): - return 'Kill all instances of an application' - - @classmethod - def get_default_params(clazz): - return { - 'name': 'firefox' - } - - @classmethod - def get_param_ui(clazz): - return { - 'name': SelectAppParam('Application') - } - def execute(self): - p = subprocess.Popen(['ps', '-A'], stdout=subprocess.PIPE) + p = subprocess.Popen(['ps', '-A', 'h'], stdout=subprocess.PIPE) out, err = p.communicate() for line in out.splitlines(): if self._params['name'].upper() in line.upper(): @@ -134,105 +125,168 @@ +class WnckScreenMonitor(CuttleEvent): + CATEGORY = 'Applications' + - -class ApplicationStart(CuttleEvent): - def __init__(self): + def __init__(self, event): CuttleEvent.__init__(self) self._screen = Wnck.Screen.get_default() + self._event = event - @classmethod - def get_name(clazz): - return "Application starts" - - @classmethod - def get_descp(clazz): - return "React when a specified application starts" - - @classmethod - def get_default_params(clazz): - return { - 'application' : 'firefox', - 'firstOnly' : False - } - - @classmethod - def get_param_ui(clazz): - return { - 'application' : SelectAppParam('Application'), - 'firstOnly' : BoolParam('Trigger only the first instance') - } - def setup(self): self._screen.force_update() - self._hid = self._screen.connect("window-opened", self.on_window_opened) - + self._hid = self._screen.connect(self._event, self.on_screen_event) + def teardown(self): self._screen.disconnect(self._hid) + + + def _app_to_pid(self, app): + pid = app.get_pid() + if pid == 0: + # seems to be QT-window, getting pid from first window + if app.get_n_windows() > 0: + return app.get_windows()[0].get_pid() + else: + return 0 + else: + return pid + + def _get_proc_name(self, proc): + name = proc.name + if '/' in name: + # process started by interpreter (e.g. python /usr/bin/terminator) + # Try to get clean name from cmdline: python foo/bar.py --> bar + if len(proc.cmdline) > 1: + name = os.path.basename(proc.cmdline[1]).split('.',1)[0] + return name + + def _count_siblings(self, procOrName): + pids = [] + ppids = [] + if isinstance(procOrName, str): + thisName = procOrName + else: + thisName = self._get_proc_name(procOrName) + + for pid in psutil.get_pid_list(): + try: + proc = psutil.Process(pid) + otherName = self._get_proc_name(proc) + if thisName == otherName: + pids.append(proc.pid) + ppids.append(proc.ppid) + except: + pass + + parented = [ppid for ppid in ppids if ppid in pids] + return len(pids) - len(parented) + + def on_screen_event(self, screen, data): + pass + + +class ApplicationStart(WnckScreenMonitor, CuttlePlugin): + NAME = 'Application starts' + DESCP = "React when a specified application starts" + PARAMS = { + 'application' : 'firefox', + 'firstOnly' : False + } - def on_window_opened(self, screen, window): - app = window.get_application() + class Editor(CuttlePlugin.Editor): + ORDER = ['application', 'firstOnly'] + + def begin(self): + return { + 'application' : SelectAppParam('Application'), + 'firstOnly' : BoolParam('Trigger only the first instance') + } - if self._params['application'].upper() in app.get_name().upper(): - if self._params['firstOnly']: - isFirst = True - for wnd in self._screen.get_windows(): - app2 = wnd.get_application() - if app.get_name() == app2.get_name() and app2.get_pid() != app.get_pid(): - isFirst = False - break - if isFirst: - self.trigger() - else: - self.trigger() + def __init__(self): + WnckScreenMonitor.__init__(self, "window-opened") + CuttlePlugin.__init__(self) + + def on_screen_event(self, screen, data): + pid = self._app_to_pid(data.get_application()) + if pid > 0: + proc = psutil.Process(pid) + psname = self._get_proc_name(proc) + suspect = os.path.basename(self._params['application']) + + if psname == suspect: + if self._params['firstOnly']: + if self._count_siblings(proc) == 1: + self.trigger() + else: + self.trigger() -class ApplicationEnd(CuttleEvent): +class ApplicationEnd(WnckScreenMonitor, CuttlePlugin): + NAME = 'Application stops' + DESCP = "React when a specified application finishes" + CATEGORY = 'Applications' + PARAMS = { + 'application' : 'firefox', + 'lastOnly' : False + } + + class Editor(CuttlePlugin.Editor): + ORDER = ['application', 'lastOnly'] + + def begin(self): + return { + 'application' : SelectAppParam('Application'), + 'lastOnly' : BoolParam('Trigger only the last instance') + } + def __init__(self): - CuttleEvent.__init__(self) - self._screen = Wnck.Screen.get_default() + WnckScreenMonitor.__init__(self, "application-closed") + CuttlePlugin.__init__(self) + - @classmethod - def get_name(clazz): - return "Application stops" - - @classmethod - def get_descp(clazz): - return "React when a specified application finishes" - - @classmethod - def get_default_params(clazz): - return { - 'application' : 'firefox', - 'lastOnly' : False - } - - @classmethod - def get_param_ui(clazz): - return { - 'application' : SelectAppParam('Application'), - 'lastOnly' : BoolParam('Trigger only the last instance') - } - def setup(self): - self._hid = self._screen.connect("application-closed", self.on_window_closed) + WnckScreenMonitor.setup(self) + # need to manage pids on my own, because when the application-closed event is fired, the + # proc-structure is not valid anymore + self._pids = {} + for pid in psutil.get_pid_list(): + try: + _proc = psutil.Process(pid) + psname = self._get_proc_name(_proc) + self._pids[pid] = psname + except: + pass + + self._hid_open = self._screen.connect("window-opened", self.on_window_opened) def teardown(self): - self._screen.disconnect(self._hid) + WnckScreenMonitor.teardown(self) + self._screen.disconnect(self._hid_open) + self._pids = {} + + + def on_window_opened(self, screen, window): + pid = self._app_to_pid(window.get_application()) + if pid > 0: + self._pids[pid] = self._get_proc_name(psutil.Process(pid)) + - def on_window_closed(self, screen, app): - if self._params['application'].upper() in app.get_name().upper(): - if self._params['lastOnly']: - isLast = True - for wnd in self._screen.get_windows(): - app2 = wnd.get_application() - if app.get_name() == app2.get_name() and app2.get_pid() != app.get_pid(): - isLast = False - break - if isLast: - self.trigger() - else: - self.trigger() + def on_screen_event(self, screen, data): + pid = self._app_to_pid(data) + + if pid in self._pids: + psname = self._pids[pid] + suspect = os.path.basename(self._params['application']) + del self._pids[pid] + + if psname == suspect: + if self._params['lastOnly']: + if self._count_siblings(psname) == 0: + self.trigger() + else: + self.trigger() \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/screensaver.py cuttlefish-12.08/cuttlefish/plugins/screensaver.py --- cuttlefish-12.07.13/cuttlefish/plugins/screensaver.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/screensaver.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,130 +1,73 @@ from cuttlefish.actions import CuttleAction -from cuttlefish.events import CuttleEvent +from cuttlefish.events import DBusEvent +from cuttlefish.plugins import CuttlePlugin from cuttlefish.params import SelectParam, FileParam import dbus -class TurnOnScreensaver(CuttleAction): - - @classmethod - def get_name(clazz): - return 'Screensaver :: Activate' - - @classmethod - def get_descp(clazz): - return 'Turn on screensaver' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def execute(self): - bus = dbus.SessionBus() - screensaverobj = bus.get_object('org.gnome.ScreenSaver', '/') - screensaver = dbus.Interface(screensaverobj, 'org.gnome.ScreenSaver') - - screensaver.Lock() - +class ScreensaverAction(CuttleAction): + CATEGORY = "Screensaver" -class TurnOffScreensaver(CuttleAction): + def __init__(self): + CuttleAction.__init__(self) - @classmethod - def get_name(clazz): - return 'Screensaver :: Deactivate' - - @classmethod - def get_descp(clazz): - return 'Turn off screensaver' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def execute(self): + def _get_screensaver(self): bus = dbus.SessionBus() screensaverobj = bus.get_object('org.gnome.ScreenSaver', '/') - screensaver = dbus.Interface(screensaverobj, 'org.gnome.ScreenSaver') - - screensaver.SetActive(False) + return dbus.Interface(screensaverobj, 'org.gnome.ScreenSaver') +class TurnOnScreensaver(ScreensaverAction, CuttlePlugin): + NAME = 'Lock screen' + DESCP = 'Lock the screen and turn on the screensaver' + def __init__(self): + ScreensaverAction.__init__(self) + CuttlePlugin.__init__(self) + def execute(self): + self._get_screensaver().Lock() +class TurnOffScreensaver(ScreensaverAction, CuttlePlugin): + NAME = 'Unlock screen' + DESCP = 'Unlock the screen and turn off the screensaver' + def __init__(self): + ScreensaverAction.__init__(self) + CuttlePlugin.__init__(self) -class ScreensaverActivated(CuttleEvent): + def execute(self): + self._get_screensaver().SetActive(False) - def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SessionBus() - @classmethod - def get_name(clazz): - return "Screensaver turned on" - - @classmethod - def get_descp(clazz): - return "React when screensaver is turned on" - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def setup(self): - self._hid = self._bus.add_signal_receiver(self.on_active_changed, signal_name="ActiveChanged", dbus_interface="org.gnome.ScreenSaver") - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) - - def on_active_changed(self, isActive): - if isActive: - self.trigger() +class ScreensaverChanged(DBusEvent): + CATEGORY = "Screensaver" + def __init__(self, isActive): + DBusEvent.__init__(self, self._on_active_changed, 'ActiveChanged', 'org.gnome.ScreenSaver', None) + self._isActive = isActive -class ScreensaverDeactivated(CuttleEvent): + def _on_active_changed(self, isActive): + if isActive == self._isActive: + self.trigger() + +class ScreensaverActivated(ScreensaverChanged, CuttlePlugin): + NAME = "Screen is locked" + DESCP = "React when the screen is locked or screensaver starts" + def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SessionBus() + ScreensaverChanged.__init__(self, True) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return "Screensaver turned off" - - @classmethod - def get_descp(clazz): - return "React when screensaver is turned off" - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def setup(self): - self._hid = self._bus.add_signal_receiver(self.on_active_changed, signal_name="ActiveChanged", dbus_interface="org.gnome.ScreenSaver") - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) + +class ScreensaverDeactivated(ScreensaverChanged, CuttlePlugin): + NAME = "Screen is unlocked" + DESCP = "React when the screen is unlocked or screensaver stops" - def on_active_changed(self, isActive): - if not isActive: - self.trigger() \ No newline at end of file + def __init__(self): + ScreensaverChanged.__init__(self, False) + CuttlePlugin.__init__(self) \ No newline at end of file diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/udev.py cuttlefish-12.08/cuttlefish/plugins/udev.py --- cuttlefish-12.07.13/cuttlefish/plugins/udev.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/udev.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,221 +1,151 @@ from cuttlefish.actions import CuttleAction -from cuttlefish.events import CuttleEvent -from cuttlefish.params import SelectParam, FileParam +from cuttlefish.events import CuttleEvent, DBusEvent +from cuttlefish.plugins import CuttlePlugin +from cuttlefish.params import SelectParam, BoolParam from pyudev import Context, Monitor from subprocess import Popen, PIPE import threading import dbus -class USBDeviceAdded(CuttleEvent): - def __init__(self): - CuttleEvent.__init__(self) - self._ctx = Context() +class USBDeviceChanged(CuttleEvent): + CATEGORY = "Hardware" + PARAMS = { + 'devid' : ':', + 'devname' : '' + } - @classmethod - def get_name(clazz): - return "USB device plugged" - - @classmethod - def get_descp(clazz): - return "React when a specified USB device gets plugged in." - - @classmethod - def get_default_params(clazz): - return {'devid' : ':'} - - @classmethod - def get_param_ui(clazz): - devs = {} - p = Popen(['lsusb'], stdout=PIPE) - out, err = p.communicate() - for dev in map(lambda x : x.split(' ', 6)[5:], out[:-1].split('\n')): - devs[dev[0]] = dev[1] + class Editor(CuttlePlugin.Editor): + ORDER = ['devid'] + def _list_devs(self): + devs = {} + p = Popen(['lsusb'], stdout=PIPE) + out, err = p.communicate() + for dev in map(lambda x : x.split(' ', 6)[5:], out[:-1].split('\n')): + devs[dev[0]] = dev[1] - return {'devid' : SelectParam('Monitored device', devs, str)} - - def setup(self): - self._killSwitch = threading.Event() - self._thread = threading.Thread(target=self._monitor).start() - - def teardown(self): - self._killSwitch.set() - + if self._params['devid'] != ':' and self._params['devid'] not in devs: + devs[self._params['devid']] = '%s (currently not connected)' % self._params['devname'] - def _get_device(self): - idVendor, idProduct = self._params['devid'].split(':') - devs = [d for d in self._ctx.list_devices().match_attribute('idVendor', idVendor).match_attribute('idProduct', idProduct)] - # FIXME ugly, since buggy with two devices of the same kind - if len(devs) > 0: - return True, devs[0] - else: - return False, None + return devs - def _monitor(self): - isAvail, dev = self._get_device() - while not self._killSwitch.isSet(): - oldIsAvail = isAvail - isAvail, dev = self._get_device() + def begin(self): + return { + 'devid' : SelectParam('Monitored device', self._list_devs, str) + } - if isAvail != oldIsAvail and isAvail: - self.trigger() + def finish(self, ui): + self._params['devname'] = ui['devid'].get_caption() + if '(currently not connected)' in self._params['devname']: + self._params['devname'] = self._params['devname'].replace(' (currently not connected)', '') - self._killSwitch.wait(1) - - - -class USBDeviceRemoved(CuttleEvent): - - def __init__(self): + def __init__(self, isAvail): CuttleEvent.__init__(self) self._ctx = Context() + self._isAvail = isAvail - @classmethod - def get_name(clazz): - return "USB device unplugged" - - @classmethod - def get_descp(clazz): - return "React when a specified USB device gets plugged off" + def triggerOnStartup(self): + if self._is_avail() == self._isAvail: + self.trigger() - @classmethod - def get_default_params(clazz): - return {'devid' : ':'} - - @classmethod - def get_param_ui(clazz): - devs = {} - p = Popen(['lsusb'], stdout=PIPE) - out, err = p.communicate() - for dev in map(lambda x : x.split(' ', 6)[5:], out[:-1].split('\n')): - devs[dev[0]] = dev[1] - - return {'devid' : SelectParam('Monitored device', devs, str)} - + def setup(self): self._killSwitch = threading.Event() self._thread = threading.Thread(target=self._monitor).start() def teardown(self): - self._killSwitch.set() - + self._killSwitch.set() - def _get_device(self): - idVendor, idProduct = self._params['devid'].split(':') - devs = [d for d in self._ctx.list_devices().match_attribute('idVendor', idVendor).match_attribute('idProduct', idProduct)] - # FIXME ugly, since buggy with two devices of the same kind - if len(devs) > 0: - return True, devs[0] - else: - return False, None def _monitor(self): - isAvail, dev = self._get_device() + isAvail = self._is_avail() while not self._killSwitch.isSet(): oldIsAvail = isAvail - isAvail, dev = self._get_device() + isAvail = self._is_avail() - if isAvail != oldIsAvail and not isAvail: + if isAvail != oldIsAvail and isAvail == self._isAvail: self.trigger() self._killSwitch.wait(1) - -class PowerDisconnected(CuttleEvent): + def _is_avail(self): + idVendor, idProduct = self._params['devid'].split(':') + devs = [d for d in self._ctx.list_devices().match_attribute('idVendor', idVendor).match_attribute('idProduct', idProduct)] + # FIXME ugly, since maybe buggy with two devices of the same kind (at least not tested...) + return len(devs) > 0 + - def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SystemBus() - @classmethod - def get_name(clazz): - return "Power cable unplugged" - - @classmethod - def get_descp(clazz): - return "React when a the power cable is unplugged" - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - devs = {} - - return {} - - def setup(self): - self._isOnBattery = self._get_property('OnBattery') - self._hid = self._bus.add_signal_receiver(self.on_prop_changed, signal_name="Changed", dbus_interface="org.freedesktop.UPower", path='/org/freedesktop/UPower') - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) +class USBDeviceAdded(USBDeviceChanged, CuttlePlugin): + NAME = "USB device plugged in" + DESCP = "React when a specified USB device gets plugged in" + def __init__(self): + USBDeviceChanged.__init__(self, True) + CuttlePlugin.__init__(self) - def _get_property(self, prop): - obj = self._bus.get_object('org.freedesktop.UPower', '/org/freedesktop/UPower') - props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') - - return props.Get('org.freedesktop.UPower', prop) + +class USBDeviceRemoved(USBDeviceChanged, CuttlePlugin): + NAME ="USB device unplugged" + DESCP = "React when a specified USB device gets plugged off" - def on_prop_changed(self): - tmp = self._get_property('OnBattery') - if tmp != self._isOnBattery: - self._isOnBattery = tmp - if self._isOnBattery: - self.trigger() + def __init__(self): + USBDeviceChanged.__init__(self, False) + CuttlePlugin.__init__(self) + -class PowerConnected(CuttleEvent): - def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SystemBus() +class PowerChanged(DBusEvent): + CATEGORY = 'Hardware' - @classmethod - def get_name(clazz): - return "Power cable plugged in" - - @classmethod - def get_descp(clazz): - return "React when a the power cable is plugged in" - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - devs = {} - - return {} + def __init__(self, isOnBatteryValue): + DBusEvent.__init__(self, self.on_prop_changed, "Changed", "org.freedesktop.UPower", '/org/freedesktop/UPower', DBusEvent.USE_SYSTEM_BUS) + self._isOnBatteryValue = isOnBatteryValue + def triggerOnStartup(self): + if self._is_on_battery() == self._isOnBatteryValue: + self.trigger() + def setup(self): - self._isOnBattery = self._get_property('OnBattery') - self._hid = self._bus.add_signal_receiver(self.on_prop_changed, signal_name="Changed", dbus_interface="org.freedesktop.UPower", path='/org/freedesktop/UPower') + self._isOnBattery = self._is_on_battery() + DBusEvent.setup(self) - def teardown(self): - self._bus.remove_signal_receiver(self._hid) - + def on_prop_changed(self): + tmp = self._is_on_battery() + if tmp != self._isOnBattery: + self._isOnBattery = tmp + if self._isOnBattery == self._isOnBatteryValue: + self.trigger() + - def _get_property(self, prop): + def _is_on_battery(self): obj = self._bus.get_object('org.freedesktop.UPower', '/org/freedesktop/UPower') props = dbus.Interface(obj, 'org.freedesktop.DBus.Properties') - return props.Get('org.freedesktop.UPower', prop) + return props.Get('org.freedesktop.UPower', 'OnBattery') - def on_prop_changed(self): - tmp = self._get_property('OnBattery') - if tmp != self._isOnBattery: - self._isOnBattery = tmp - if not self._isOnBattery: - self.trigger() +class PowerDisconnected(PowerChanged, CuttlePlugin): + NAME = 'Power Cable unplugged' + DESCP = 'React when a the power cable is unplugged' + + def __init__(self): + PowerChanged.__init__(self, True) + CuttlePlugin.__init__(self) + + +class PowerConnected(PowerChanged, CuttlePlugin): + NAME = 'Power Cable plugged in' + DESCP = 'React when a the power cable is plugged in' + + def __init__(self): + PowerChanged.__init__(self, False) + CuttlePlugin.__init__(self) diff -Nru cuttlefish-12.07.13/cuttlefish/plugins/wlan.py cuttlefish-12.08/cuttlefish/plugins/wlan.py --- cuttlefish-12.07.13/cuttlefish/plugins/wlan.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/plugins/wlan.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,55 +1,60 @@ -from cuttlefish.params import StringParam, SelectParam, IntParam -from cuttlefish.events import CuttleEvent +from cuttlefish.params import StringParam, SelectParam, IntParam, BoolParam +from cuttlefish.plugins import CuttlePlugin +from cuttlefish.events import CuttleEvent, DBusEvent from cuttlefish.actions import CuttleAction import threading import dbus -class WlanConnects(CuttleEvent): +def _convert_ssid(SSIDbyteArray): + ssid = "" + for byte in SSIDbyteArray: + ssid += chr(byte) + return ssid + + +class WlanConnects(DBusEvent, CuttlePlugin): + NAME = "Connect to WLAN" + DESCP = "React when connecting to a specified wireless network" + CATEGORY = "Network" + PARAMS = { + 'ssid' : '' + } + #http://projects.gnome.org/NetworkManager/developers/api/09/spec.html#type-NM_STATE NM_STATE_CONNECTED_GLOBAL = 70 + class Editor(CuttlePlugin.Editor): + def begin(self): + ssids = {} + bus = dbus.SystemBus() + setsobj = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Settings') + settings = dbus.Interface(setsobj, 'org.freedesktop.NetworkManager.Settings') + for cspath in settings.ListConnections(): + csobj = bus.get_object('org.freedesktop.NetworkManager', cspath) + connecSettings = dbus.Interface(csobj, 'org.freedesktop.NetworkManager.Settings.Connection') + config = connecSettings.GetSettings() + if '802-11-wireless' in config: + ssid = _convert_ssid(config['802-11-wireless']['ssid']) + ssids[ssid] = ssid + + return { + 'ssid' : SelectParam('SSID of the wireless network', ssids, str) + } + + def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SystemBus() + DBusEvent.__init__(self, self._on_connec_state_changed, "StateChanged", "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", DBusEvent.USE_SYSTEM_BUS) + CuttlePlugin.__init__(self) - @classmethod - def get_name(clazz): - return "Connect to WLAN" - - @classmethod - def get_descp(clazz): - return "React when connecting to a specified wireless network" - - @classmethod - def get_default_params(clazz): - return {'ssid' : ''} - - @classmethod - def get_param_ui(clazz): - ssids = {} - bus = dbus.SystemBus() - setsobj = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager/Settings') - settings = dbus.Interface(setsobj, 'org.freedesktop.NetworkManager.Settings') - for cspath in settings.ListConnections(): - csobj = bus.get_object('org.freedesktop.NetworkManager', cspath) - connecSettings = dbus.Interface(csobj, 'org.freedesktop.NetworkManager.Settings.Connection') - config = connecSettings.GetSettings() - if '802-11-wireless' in config: - ssid = clazz._convert_ssid(config['802-11-wireless']['ssid']) - ssids[ssid] = ssid - - - return {'ssid' : SelectParam('SSID of the wireless network', ssids, str)} - - def setup(self): - self._hid = self._bus.add_signal_receiver(self.on_connecstate_changed, signal_name="StateChanged", dbus_interface="org.freedesktop.NetworkManager", path="/org/freedesktop/NetworkManager") - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) - - def on_connecstate_changed(self, state): + def triggerOnStartup(self): + nmobj = self._bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager') + nmprops = dbus.Interface(nmobj, 'org.freedesktop.DBus.Properties') + self._on_connec_state_changed(nmprops.Get('org.freedesktop.NetworkManager', 'State')) + + def _on_connec_state_changed(self, state): + self.logger.debug("state %d" % state) if state == self.NM_STATE_CONNECTED_GLOBAL: ssid = self._get_ssid() if ssid and ssid == self._params['ssid']: @@ -66,51 +71,36 @@ apobj = self._bus.get_object('org.freedesktop.NetworkManager', appath) approps = dbus.Interface(apobj, 'org.freedesktop.DBus.Properties') - return self._convert_ssid(approps.Get('org.freedesktop.NetworkManager.AccessPoint', 'Ssid')) + try: + return _convert_ssid(approps.Get('org.freedesktop.NetworkManager.AccessPoint', 'Ssid')) + except: + continue return False - @classmethod - def _convert_ssid(clazz, SSIDbyteArray): - ssid = "" - for byte in SSIDbyteArray: - ssid += chr(byte) - return ssid - +class WlanDisconnects(DBusEvent, CuttlePlugin): + NAME = "Disconnect from WLAN/LAN" + DESCP = "React when losing the network connection" + CATEGORY = "Network" + PARAMS = { + 'delay' : 5 + } -class WlanDisconnects(CuttleEvent): #http://projects.gnome.org/NetworkManager/developers/api/09/spec.html#type-NM_STATE NM_STATE_DISCONNECTED = 20 + + class Editor(CuttlePlugin.Editor): + def begin(self): + return {'delay' : IntParam('Only if disconnected longer than (in sec)')} + def __init__(self): - CuttleEvent.__init__(self) - self._bus = dbus.SystemBus() + DBusEvent.__init__(self, self._on_connec_state_changed, "StateChanged", "org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager", DBusEvent.USE_SYSTEM_BUS) + CuttlePlugin.__init__(self) self._reconnect = threading.Event() - - @classmethod - def get_name(clazz): - return "Disconnect from WLAN/LAN" - - @classmethod - def get_descp(clazz): - return "React when loosing the network connection" - @classmethod - def get_default_params(clazz): - return {'delay' : 5} - - @classmethod - def get_param_ui(clazz): - return {'delay' : IntParam('Only if disconnected longer than (in sec)')} - - def setup(self): - self._hid = self._bus.add_signal_receiver(self.on_connecstate_changed, signal_name="StateChanged", dbus_interface="org.freedesktop.NetworkManager", path="/org/freedesktop/NetworkManager") - - def teardown(self): - self._bus.remove_signal_receiver(self._hid) - - def on_connecstate_changed(self, state): + def _on_connec_state_changed(self, state): if state == self.NM_STATE_DISCONNECTED: if self._params['delay'] <= 0: self.trigger() @@ -126,54 +116,34 @@ +class ChangeWLAN(CuttleAction): + CATEGORY = "Network" -class TurnOnWLAN(CuttleAction): + def __init__(self, active): + CuttleAction.__init__(self) + self._active = active - @classmethod - def get_name(clazz): - return 'WLAN :: Activate' - - @classmethod - def get_descp(clazz): - return 'Turn on wireless networking' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - def execute(self): bus = dbus.SystemBus(); nmobj = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager') nmprops = dbus.Interface(nmobj, 'org.freedesktop.DBus.Properties') - nmprops.Set('org.freedesktop.NetworkManager', 'WirelessEnabled', True) + nmprops.Set('org.freedesktop.NetworkManager', 'WirelessEnabled', self._active) -class TurnOffWLAN(CuttleAction): - @classmethod - def get_name(clazz): - return 'WLAN :: Deactivate' - - @classmethod - def get_descp(clazz): - return 'Turn off wireless networking' - - @classmethod - def get_default_params(clazz): - return {} - - @classmethod - def get_param_ui(clazz): - return {} - - def execute(self): - bus = dbus.SystemBus(); - nmobj = bus.get_object('org.freedesktop.NetworkManager', '/org/freedesktop/NetworkManager') - nmprops = dbus.Interface(nmobj, 'org.freedesktop.DBus.Properties') - nmprops.Set('org.freedesktop.NetworkManager', 'WirelessEnabled', False) +class TurnOnWLAN(ChangeWLAN, CuttlePlugin): + NAME = 'Activate WLAN' + DESCP = 'Turn on wireless networking' + + def __init__(self): + ChangeWLAN.__init__(self, True) + CuttlePlugin.__init__(self) +class TurnOffWLAN(ChangeWLAN, CuttlePlugin): + NAME = 'Deactivate WLAN' + DESCP = 'Turn off wireless networking' + + def __init__(self): + ChangeWLAN.__init__(self, False) + CuttlePlugin.__init__(self) diff -Nru cuttlefish-12.07.13/cuttlefish/reflexes.py cuttlefish-12.08/cuttlefish/reflexes.py --- cuttlefish-12.07.13/cuttlefish/reflexes.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/reflexes.py 2012-08-25 10:22:17.000000000 +0000 @@ -19,13 +19,13 @@ cdir = os.path.abspath(os.path.expanduser('~/.cuttlefish')) if not os.path.isdir(cdir): os.mkdir(cdir) - dbfile = os.path.join(cdir, 'reflexes.db') - if not os.path.isfile(dbfile): - self._db = sqlite3.connect(dbfile) - self._db.execute('CREATE TABLE %s (record_id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT);' % self._record_type) - else: - self._db = sqlite3.connect(dbfile) + self._dbfile = os.path.join(cdir, 'reflexes.db') + if not os.path.isfile(self._dbfile): + c = sqlite3.connect(self._dbfile) + c.execute('CREATE TABLE %s (record_id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT);' % self._record_type) + c.close() + def create(self): record = self._record_class(self._record_class.getDefaults()) @@ -39,35 +39,43 @@ def list(self): records = [] - for row in self._db.execute('SELECT record_id, data FROM %s' % self._record_type): + c = sqlite3.connect(self._dbfile) + for row in c.execute('SELECT record_id, data FROM %s' % self._record_type): data = json.loads(row[1]) record = self._record_class(self.__mergeDefaults(data)) record.record_id = row[0] records.append(record) + c.close() return records def get(self, record_id): - row = self._db.execute('SELECT record_id, data FROM %s WHERE record_id = ?' % self._record_type, (record_id,)).fetchone() + c = sqlite3.connect(self._dbfile) + row = c.execute('SELECT record_id, data FROM %s WHERE record_id = ?' % self._record_type, (record_id,)).fetchone() + c.close() data = json.loads(row[1]) record = self._record_class(self.__mergeDefaults(data)) record.record_id = row[0] return record def save(self, record): + c = sqlite3.connect(self._dbfile) data = json.dumps(record.get_data()) if record.record_id: - self._db.execute('UPDATE %s SET data = ? WHERE record_id = ?' % self._record_type, (data, record.record_id,)) + c.execute('UPDATE %s SET data = ? WHERE record_id = ?' % self._record_type, (data, record.record_id,)) else: data = json.dumps(record.get_data()) - record_id = self._db.execute('INSERT INTO %s VALUES (NULL, ?)' % self._record_type, (data,)).lastrowid + record_id = c.execute('INSERT INTO %s VALUES (NULL, ?)' % self._record_type, (data,)).lastrowid record.record_id = record_id - self._db.commit() + c.commit() + c.close() self.emit('save', record) def delete(self, record_id): - self._db.execute('DELETE FROM %s WHERE record_id = ?' % self._record_type, (record_id,)) - self._db.commit() + c = sqlite3.connect(self._dbfile) + c.execute('DELETE FROM %s WHERE record_id = ?' % self._record_type, (record_id,)) + c.commit() + c.close() self.emit('delete', record_id) @@ -97,12 +105,14 @@ def getDefaults(clazz): return { 'name': 'New Reflex', + 'showNotification' : True, + 'listInIndicator' : True, 'event': { 'isActive' : False, 'type' : '', 'params' : {} }, - 'actions' : [] + 'actions' : [], } diff -Nru cuttlefish-12.07.13/cuttlefish/ui.py cuttlefish-12.08/cuttlefish/ui.py --- cuttlefish-12.07.13/cuttlefish/ui.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/cuttlefish/ui.py 2012-08-25 10:22:17.000000000 +0000 @@ -1,8 +1,7 @@ from gi.repository import Gtk from cuttlefish.events import EventManager - - - +from cuttlefish.plugins import CuttlePlugin +from cuttlefish.params import BoolParam @@ -21,9 +20,9 @@ def _set_details_text(self, label, reflex): e = self._eventMgr.get_class(reflex['event']['type']) - if e.get_name() != 'None' and reflex['event']['isActive']: + if e.NAME != 'UNKOWN' and reflex['event']['isActive']: label.set_markup('%s\nTriggering \'%s\' will cause a reaction consisting of %d actions.' % - (reflex['name'], e.get_name(), len(reflex['actions']))) + (reflex['name'], e.NAME, len(reflex['actions']))) else: label.set_markup('%s\nManually starting this reflex will cause a reaction consisting of %d actions.' % (reflex['name'], len(reflex['actions']))) @@ -49,9 +48,9 @@ lblDetails.set_use_markup(True) self._set_details_text(lblDetails, reflex) - settings = Gtk.Settings.get_default() - tmp = settings.props.gtk_button_images - settings.props.gtk_button_images = True + #settings = Gtk.Settings.get_default() + #tmp = settings.props.gtk_button_images + #settings.props.gtk_button_images = True btnEdit = Gtk.Button('gtk-edit', use_stock=True) #btnDelete = Gtk.Button('gtk-delete', use_stock=True) @@ -82,38 +81,64 @@ class DynamicUI: - def __init__(self, frame): - self._elements = {} - self._frame = frame - - def clear(self): - for key, ui in self._elements.items(): - ui.destroy() - self._elements = {} - - def setup(self, element): - self._elements = element.get_param_ui() - params = element.get_params() - - for key, ui in self._elements.items(): - lbl = ui.get_label() - widget = ui.get_widget() - - lbl.set_halign(Gtk.Align.END) - widget.set_halign(Gtk.Align.START) - - self._frame.add(lbl) - self._frame.attach_next_to(widget, lbl, Gtk.PositionType.RIGHT, 1, 1) - ui.set_value(params[key]) - - self._frame.show_all() - - def get_values(self): - result = {} - for (key, ui) in self._elements.items(): - result[key] = ui.get_value() - return result - - def set_sensitive(self, sensitive): - for key, ui in self._elements.items(): - ui.set_sensitive(sensitive) \ No newline at end of file + def __init__(self, frame): + self._elements = {} + self._editor = None + self._frame = frame + + def clear(self): + for key, ui in self._elements.items(): + ui.destroy() + self._elements = {} + + def setup(self, element): + if not hasattr(element, 'Editor'): + self._elements = {} + self._editor = None + return + + self._params = element.get_params() + self._editor = element.Editor() + self._editor.set_params(self._params) + if hasattr(element, 'triggerOnStartup') and hasattr(element.triggerOnStartup, '__call__') and 'triggerOnStartup' not in self._params: + self._params['triggerOnStartup'] = False + + self._elements = self._editor.begin() + order = hasattr(self._editor, 'ORDER') and self._editor.ORDER or self._elements + + if hasattr(element, 'triggerOnStartup') and hasattr(element.triggerOnStartup, '__call__'): + self._elements['triggerOnStartup'] = BoolParam('Also check on startup') + if 'triggerOnStartup' not in order: + order.append('triggerOnStartup') + + for key in order: + lbl = self._elements[key].get_label() + widget = self._elements[key].get_widget() + + lbl.set_halign(Gtk.Align.END) + widget.set_halign(Gtk.Align.START) + + self._frame.add(lbl) + self._frame.attach_next_to(widget, lbl, Gtk.PositionType.RIGHT, 1, 1) + self._elements[key].set_value(self._params[key]) + + self._frame.show_all() + + def get_values(self): + result = {} + for (key, ui) in self._elements.items(): + tmp = ui.get_value() + if not tmp is None: + result[key] = tmp + else: + result[key] = self._params[key] + + if self._editor: + self._editor.set_params(result) + self._editor.finish(self._elements) + result = self._editor.get_params() + return result + + def set_sensitive(self, sensitive): + for key, ui in self._elements.items(): + ui.set_sensitive(sensitive) \ No newline at end of file Binary files /tmp/MibGYED0Ra/cuttlefish-12.07.13/data/glib-2.0/schemas/gschemas.compiled and /tmp/lk5BLIWa1K/cuttlefish-12.08/data/glib-2.0/schemas/gschemas.compiled differ diff -Nru cuttlefish-12.07.13/data/glib-2.0/schemas/net.launchpad.cuttlefish.gschema.xml cuttlefish-12.08/data/glib-2.0/schemas/net.launchpad.cuttlefish.gschema.xml --- cuttlefish-12.07.13/data/glib-2.0/schemas/net.launchpad.cuttlefish.gschema.xml 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/data/glib-2.0/schemas/net.launchpad.cuttlefish.gschema.xml 2012-07-29 16:53:51.000000000 +0000 @@ -6,5 +6,10 @@ Show Notifications Enable this to show notifications when a reflex is stimulated and the definied reaction is started. + + false + Enable Logging + Enable this to let cuttlefish log when a reflex is stimulated and the definied reaction is started. This includes any debug information in external plugins. + diff -Nru cuttlefish-12.07.13/data/ui/AboutCuttlefishDialog.ui cuttlefish-12.08/data/ui/AboutCuttlefishDialog.ui --- cuttlefish-12.07.13/data/ui/AboutCuttlefishDialog.ui 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/data/ui/AboutCuttlefishDialog.ui 2012-08-25 10:22:17.000000000 +0000 @@ -9,7 +9,7 @@ ../media/tentacle.svg normal Cuttlefish - 12.07.13 + 12.08 Copyright (C) 2012 Alexander von Bremen-Kuehne <noneed4anick@gmail.com> Cuttlefish is an event driven tool that will adapt the appearance and settings of your system according to diff -Nru cuttlefish-12.07.13/data/ui/CuttlefishWindow.ui cuttlefish-12.08/data/ui/CuttlefishWindow.ui --- cuttlefish-12.07.13/data/ui/CuttlefishWindow.ui 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/data/ui/CuttlefishWindow.ui 2012-07-29 23:59:11.000000000 +0000 @@ -83,11 +83,12 @@ + True False - + gtk-close False True @@ -95,6 +96,18 @@ True True + + + + + + gtk-quit + False + True + False + True + True + @@ -128,6 +141,41 @@ + + False + True + False + Plugins + True + + + True + False + + + False + True + False + Edit own plugins + True + + + + + False + True + False + Reload plugins + True + + + + + + + + + gtk-preferences False @@ -165,7 +213,6 @@ Contents False - True False image2 False @@ -411,11 +458,11 @@ True False end - Activated by Stimulus + Activated by stimulus 0 - 1 + 3 1 1 @@ -430,7 +477,7 @@ 1 - 1 + 3 1 1 @@ -444,7 +491,7 @@ 0 - 2 + 4 1 1 @@ -459,8 +506,65 @@ start False half - - + + + + 1 + 4 + 1 + 1 + + + + + True + False + end + Show notification + + + 0 + 1 + 1 + 1 + + + + + True + False + end + List in indicator menu + + + 0 + 2 + 1 + 1 + + + + + False + True + True + start + False + + + 1 + 1 + 1 + 1 + + + + + False + True + True + start + False 1 diff -Nru cuttlefish-12.07.13/data/ui/PluginbeginnerDialog.ui cuttlefish-12.08/data/ui/PluginbeginnerDialog.ui --- cuttlefish-12.07.13/data/ui/PluginbeginnerDialog.ui 1970-01-01 00:00:00.000000000 +0000 +++ cuttlefish-12.08/data/ui/PluginbeginnerDialog.ui 2012-07-23 07:49:22.000000000 +0000 @@ -0,0 +1,139 @@ + + + + + + 400 + 150 + False + 5 + center-on-parent + ../media/tentacle.svg + normal + + + True + False + vertical + 2 + + + True + False + end + + + + + + gtk-ok + False + True + True + True + False + True + + + False + False + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + True + + + True + False + No plugin-file found! + + + + + + False + True + 0 + + + + + True + False + Created: ~/.cuttlefish/plugins/myplugins.py + + + False + True + 1 + + + + + True + False + True + + + True + False + Haven't worked with plugins yet? + + + False + True + 0 + + + + + Check out the tutorial on youtube + False + True + True + True + True + False + none + http://www.youtube.com/watch?v=zYUItu_dO0I + + + False + True + 1 + + + + + False + True + 2 + + + + + True + True + 1 + + + + + + btn_ok + + + diff -Nru cuttlefish-12.07.13/data/ui/PreferencesCuttlefishDialog.ui cuttlefish-12.08/data/ui/PreferencesCuttlefishDialog.ui --- cuttlefish-12.07.13/data/ui/PreferencesCuttlefishDialog.ui 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/data/ui/PreferencesCuttlefishDialog.ui 2012-07-29 17:08:21.000000000 +0000 @@ -77,7 +77,7 @@ True False - end + start 0 Show Notifications True @@ -108,7 +108,7 @@ True False - end + start Autostart @@ -133,6 +133,35 @@ 1 + + + True + False + start + Enable logging + + + 0 + 2 + 1 + 1 + + + + + False + True + True + start + False + + + 1 + 2 + 1 + 1 + + False diff -Nru cuttlefish-12.07.13/data/ui/SelectDialog.ui cuttlefish-12.08/data/ui/SelectDialog.ui --- cuttlefish-12.07.13/data/ui/SelectDialog.ui 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/data/ui/SelectDialog.ui 2012-07-17 20:47:53.000000000 +0000 @@ -69,7 +69,7 @@ True False start - Plesae select an action type + Please select an action type @@ -85,66 +85,92 @@ True False - 10 + 20 20 - + True False - start - Description + No description found + True 0 - 1 - 1 + 2 + 2 1 - + True - False - No description found - True + True + + gtk-clear + Filter + + - 1 - 1 - 1 + 0 + 0 + 2 1 - + + 150 + 250 True - False - + True + in + + + True + True + + + + + + - 1 - 0 + 0 + 1 1 1 - + + 250 + 250 True - False - start - Name + True + in + + + True + True + + + + + + - 0 - 0 + 1 + 1 1 1 - False + True True 1 diff -Nru cuttlefish-12.07.13/data/ui/pluginbeginner_dialog.xml cuttlefish-12.08/data/ui/pluginbeginner_dialog.xml --- cuttlefish-12.07.13/data/ui/pluginbeginner_dialog.xml 1970-01-01 00:00:00.000000000 +0000 +++ cuttlefish-12.08/data/ui/pluginbeginner_dialog.xml 2012-07-17 20:46:23.000000000 +0000 @@ -0,0 +1,9 @@ + + + + + + diff -Nru cuttlefish-12.07.13/debian/changelog cuttlefish-12.08/debian/changelog --- cuttlefish-12.07.13/debian/changelog 2012-08-31 06:33:19.000000000 +0000 +++ cuttlefish-12.08/debian/changelog 2012-09-03 12:45:06.000000000 +0000 @@ -1,3 +1,10 @@ +cuttlefish (12.08-extras12.04.1) precise; urgency=low + + * New Bugfix Release (Based on cuttlefish PPA) to fix installability. + (LP: #1044845) + + -- Alex Sat, 25 Aug 2012 12:22:42 +0200 + cuttlefish (12.07.13-extras12.04.1) precise; urgency=low * Initial release. diff -Nru cuttlefish-12.07.13/debian/control cuttlefish-12.08/debian/control --- cuttlefish-12.07.13/debian/control 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/debian/control 2012-09-03 12:32:42.000000000 +0000 @@ -1,21 +1,19 @@ Source: cuttlefish Section: python Priority: extra -Build-Depends: debhelper (>= 8), - libglib2.0-bin, - python (>= 2.6.6-3~), +Build-Depends: debhelper (>= 8), libglib2.0-bin, python (>= 2.6.6-3~), python-distutils-extra (>= 2.10) Maintainer: Alexander von Bremen-Kühne Standards-Version: 3.9.3 -XS-Python-Version: current +X-Python-Version: >= 2.6 Package: cuttlefish Architecture: all -XB-Python-Version: ${python:Versions} Depends: ${misc:Depends}, ${python:Depends}, gir1.2-notify-0.7, python-xdg, + python-psutil, gir1.2-gtk-3.0, python-pyudev, python-dbus, @@ -27,11 +25,6 @@ gir1.2-gmenu-3.0, gir1.2-appindicator3-0.1, yelp -Description: Realises reflexes on your computer - Cuttlefish is a simple tool, which realises reflexes on your computer - by executing actions when specific events are triggered. - You can configure the tool in such way that cuttlefish can - for example change your default printer according - to the currently connected wireless network, - or lock/unlock your computer, when a specific - USB device is connected/disconnected. +Description: simple reaction based tool + Cuttlefish is an event driven tool that will adapt the appearance and + settings of your system according to the environment diff -Nru cuttlefish-12.07.13/debian/rules cuttlefish-12.08/debian/rules --- cuttlefish-12.07.13/debian/rules 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/debian/rules 2012-08-25 10:22:42.000000000 +0000 @@ -7,11 +7,12 @@ endif override_dh_auto_install: - dh_auto_install -- --install-scripts=/opt/extras.ubuntu.com/cuttlefish --install-data=/opt/extras.ubuntu.com/cuttlefish --install-lib=/opt/extras.ubuntu.com/cuttlefish + dh_auto_install -- --install-scripts=/opt/extras.ubuntu.com/cuttlefish/bin --install-data=/opt/extras.ubuntu.com/cuttlefish --install-lib=/opt/extras.ubuntu.com/cuttlefish override_dh_python2: dh_python2 /opt/extras.ubuntu.com/cuttlefish + override_dh_install: dh_install mkdir -p debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/bin @@ -20,7 +21,7 @@ mkdir -p debian/cuttlefish/usr/share/applications; \ mv debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/share/applications/cuttlefish.desktop debian/cuttlefish/usr/share/applications/extras-cuttlefish.desktop; \ rmdir --ignore-fail-on-non-empty debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/share/applications; \ - sed -i 's|Exec=.*|Exec=/opt/extras.ubuntu.com/cuttlefish/bin/cuttlefish|' debian/cuttlefish/usr/share/applications/extras-cuttlefish.desktop; \ + sed -i 's|Exec=[^ ]*|Exec=/opt/extras.ubuntu.com/cuttlefish/bin/cuttlefish|' debian/cuttlefish/usr/share/applications/extras-cuttlefish.desktop; \ sed -i 's|Icon=/usr/|Icon=/opt/extras.ubuntu.com/cuttlefish/|' debian/cuttlefish/usr/share/applications/extras-cuttlefish.desktop; \ fi grep -RlZ 'import gettext' debian/cuttlefish/* | xargs -0 -r sed -i 's|\(import\) gettext$$|\1 locale|' @@ -28,4 +29,4 @@ grep -RlZ "gettext.textdomain('cuttlefish')" debian/cuttlefish/* | xargs -0 -r sed -i "s|gettext\(\.textdomain('cuttlefish')\)|locale\.bindtextdomain('cuttlefish', '/opt/extras.ubuntu.com/cuttlefish/share/locale')\nlocale\1|" sed -i "s|__cuttlefish_data_directory__ =.*|__cuttlefish_data_directory__ = '/opt/extras.ubuntu.com/cuttlefish/share/cuttlefish/'|" debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/cuttlefish*/cuttlefishconfig.py sed -i 's| sys.path.insert(0, opt_path)|\0\n os.putenv("XDG_DATA_DIRS", "%s:%s" % ("/opt/extras.ubuntu.com/cuttlefish/share/", os.getenv("XDG_DATA_DIRS", "")))|' debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/bin/cuttlefish - if [ -d debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/share/glib-2.0/schemas ]; then glib-compile-schemas debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/share/glib-2.0/schemas; fi + if [ -d debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/share/glib-2.0/schemas ]; then glib-compile-schemas debian/cuttlefish/opt/extras.ubuntu.com/cuttlefish/share/glib-2.0/schemas; fi \ No newline at end of file diff -Nru cuttlefish-12.07.13/po/cuttlefish.pot cuttlefish-12.08/po/cuttlefish.pot --- cuttlefish-12.07.13/po/cuttlefish.pot 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/po/cuttlefish.pot 2012-07-10 11:23:17.000000000 +0000 @@ -8,7 +8,7 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2012-07-09 16:13+0200\n" +"POT-Creation-Date: 2012-07-10 13:23+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" diff -Nru cuttlefish-12.07.13/setup.py cuttlefish-12.08/setup.py --- cuttlefish-12.07.13/setup.py 2012-08-31 06:32:22.000000000 +0000 +++ cuttlefish-12.08/setup.py 2012-08-25 10:22:17.000000000 +0000 @@ -87,7 +87,7 @@ DistUtilsExtra.auto.setup( name='cuttlefish', - version='12.07.13', + version='12.08', license='GPL-3', author='Alexander von Bremen-Kühne', author_email='noneed4anick@gmail.com',