diff options
Diffstat (limited to 'src/jarabe/model/shell.py')
-rw-r--r-- | src/jarabe/model/shell.py | 255 |
1 files changed, 158 insertions, 97 deletions
diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py index e03e0f7..63f6173 100644 --- a/src/jarabe/model/shell.py +++ b/src/jarabe/model/shell.py @@ -27,13 +27,16 @@ import dbus from sugar import wm from sugar import dispatch from sugar.graphics.xocolor import XoColor -from sugar.presence import presenceservice from jarabe.model.bundleregistry import get_registry +from jarabe.model import neighborhood + +_SERVICE_NAME = 'org.laptop.Activity' +_SERVICE_PATH = '/org/laptop/Activity' +_SERVICE_INTERFACE = 'org.laptop.Activity' + +_model = None -_SERVICE_NAME = "org.laptop.Activity" -_SERVICE_PATH = "/org/laptop/Activity" -_SERVICE_INTERFACE = "org.laptop.Activity" class Activity(gobject.GObject): """Activity which appears in the "Home View" of the Sugar shell @@ -46,10 +49,9 @@ class Activity(gobject.GObject): __gtype_name__ = 'SugarHomeActivity' - __gproperties__ = { - 'launching' : (bool, None, None, False, - gobject.PARAM_READWRITE), - } + LAUNCHING = 0 + LAUNCH_FAILED = 1 + LAUNCHED = 2 def __init__(self, activity_info, activity_id, window=None): """Initialise the HomeActivity @@ -60,19 +62,20 @@ class Activity(gobject.GObject): the "type" of activity being created. activity_id -- unique identifier for this instance of the activity type - window -- Main WnckWindow of the activity + _windows -- WnckWindows registered for the activity. The lowest + one in the stack is the main window. """ gobject.GObject.__init__(self) - self._window = None + self._windows = [] self._service = None self._activity_id = activity_id self._activity_info = activity_info self._launch_time = time.time() - self._launching = True + self._launch_status = Activity.LAUNCHING if window is not None: - self.set_window(window) + self.add_window(window) self._retrieve_service() @@ -81,18 +84,32 @@ class Activity(gobject.GObject): bus = dbus.SessionBus() self._name_owner_changed_handler = bus.add_signal_receiver( self._name_owner_changed_cb, - signal_name="NameOwnerChanged", - dbus_interface="org.freedesktop.DBus") + signal_name='NameOwnerChanged', + dbus_interface='org.freedesktop.DBus') - def set_window(self, window): - """Set the window for the activity + self._launch_completed_hid = get_model().connect('launch-completed', + self.__launch_completed_cb) + self._launch_failed_hid = get_model().connect('launch-failed', + self.__launch_failed_cb) - We allow resetting the window for an activity so that we - can replace the launcher once we get its real window. - """ + def get_launch_status(self): + return self._launch_status + + launch_status = gobject.property(getter=get_launch_status) + + def add_window(self, window): + """Add a window to the windows stack.""" if not window: - raise ValueError("window must be valid") - self._window = window + raise ValueError('window must be valid') + self._windows.append(window) + + def remove_window_by_xid(self, xid): + """Remove a window from the windows stack.""" + for wnd in self._windows: + if wnd.get_xid() == xid: + self._windows.remove(wnd) + return True + return False def get_service(self): """Get the activity service @@ -106,8 +123,8 @@ class Activity(gobject.GObject): def get_title(self): """Retrieve the application's root window's suggested title""" - if self._window: - return self._window.get_name() + if self._windows: + return self._windows[0].get_name() else: return '' @@ -135,21 +152,19 @@ class Activity(gobject.GObject): have an entry (implying that this is not a Sugar-shared application) uses the local user's profile colour for the icon. """ - pservice = presenceservice.get_instance() - # HACK to suppress warning in logs when activity isn't found # (if it's locally launched and not shared yet) activity = None - for act in pservice.get_activities(): - if self._activity_id == act.props.id: + for act in neighborhood.get_model().get_activities(): + if self._activity_id == act.activity_id: activity = act break if activity != None: - return XoColor(activity.props.color) + return activity.props.color else: client = gconf.client_get_default() - return XoColor(client.get_string("/desktop/sugar/user/color")) + return XoColor(client.get_string('/desktop/sugar/user/color')) def get_activity_id(self): """Retrieve the "activity_id" passed in to our constructor @@ -161,31 +176,44 @@ class Activity(gobject.GObject): def get_xid(self): """Retrieve the X-windows ID of our root window""" - if self._window is not None: - return self._window.get_xid() + if self._windows: + return self._windows[0].get_xid() else: return None + def has_xid(self, xid): + """Check if an X-window with the given xid is in the windows stack""" + if self._windows: + for wnd in self._windows: + if wnd.get_xid() == xid: + return True + return False + def get_window(self): """Retrieve the X-windows root window of this application - This was stored by the set_window method, which was + This was stored by the add_window method, which was called by HomeModel._add_activity, which was called via a callback that looks for all 'window-opened' events. + We keep a stack of the windows. The lowest window in the + stack that is still valid we consider the main one. + HomeModel currently uses a dbus service query on the activity to determine to which HomeActivity the newly launched window belongs. """ - return self._window + if self._windows: + return self._windows[0] + return None def get_type(self): """Retrieve the activity bundle id for future reference""" - if self._window is None: + if not self._windows: return None else: - return wm.get_bundle_id(self._window) + return wm.get_bundle_id(self._windows[0]) def is_journal(self): """Returns boolean if the activity is of type JournalActivity""" @@ -201,7 +229,9 @@ class Activity(gobject.GObject): def get_pid(self): """Returns the activity's PID""" - return self._window.get_pid() + if not self._windows: + return None + return self._windows[0].get_pid() def get_bundle_path(self): """Returns the activity's bundle directory""" @@ -220,18 +250,10 @@ class Activity(gobject.GObject): def equals(self, activity): if self._activity_id and activity.get_activity_id(): return self._activity_id == activity.get_activity_id() - if self._window.get_xid() and activity.get_xid(): - return self._window.get_xid() == activity.get_xid() + if self._windows[0].get_xid() and activity.get_xid(): + return self._windows[0].get_xid() == activity.get_xid() return False - def do_set_property(self, pspec, value): - if pspec.name == 'launching': - self._launching = value - - def do_get_property(self, pspec): - if pspec.name == 'launching': - return self._launching - def _get_service_name(self): if self._activity_id: return _SERVICE_NAME + self._activity_id @@ -245,17 +267,24 @@ class Activity(gobject.GObject): try: bus = dbus.SessionBus() proxy = bus.get_object(self._get_service_name(), - _SERVICE_PATH + "/" + self._activity_id) + _SERVICE_PATH + '/' + self._activity_id) self._service = dbus.Interface(proxy, _SERVICE_INTERFACE) except dbus.DBusException: self._service = None def _name_owner_changed_cb(self, name, old, new): if name == self._get_service_name(): - self._retrieve_service() - self.set_active(True) - self._name_owner_changed_handler.remove() - self._name_owner_changed_handler = None + if old and not new: + logging.debug('Activity._name_owner_changed_cb: ' \ + 'activity %s went away', name) + self._name_owner_changed_handler.remove() + self._name_owner_changed_handler = None + self._service = None + elif not old and new: + logging.debug('Activity._name_owner_changed_cb: ' \ + 'activity %s started up', name) + self._retrieve_service() + self.set_active(True) def set_active(self, state): """Propagate the current state to the activity object""" @@ -268,7 +297,24 @@ class Activity(gobject.GObject): pass def _set_active_error(self, err): - logging.error("set_active() failed: %s", err) + logging.error('set_active() failed: %s', err) + + def _set_launch_status(self, value): + get_model().disconnect(self._launch_completed_hid) + get_model().disconnect(self._launch_failed_hid) + self._launch_completed_hid = None + self._launch_failed_hid = None + self._launch_status = value + self.notify('launch_status') + + def __launch_completed_cb(self, model, home_activity): + if home_activity is self: + self._set_launch_status(Activity.LAUNCHED) + + def __launch_failed_cb(self, model, home_activity): + if home_activity is self: + self._set_launch_status(Activity.LAUNCH_FAILED) + class ShellModel(gobject.GObject): """Model of the shell (activity management) @@ -286,27 +332,22 @@ class ShellModel(gobject.GObject): """ __gsignals__ = { - 'activity-added': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'activity-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + 'activity-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'activity-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), 'active-activity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), + ([gobject.TYPE_PYOBJECT])), 'tabbing-activity-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-started': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-completed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])), - 'launch-failed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, - ([gobject.TYPE_PYOBJECT])) + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-completed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'launch-failed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), } ZOOM_MESH = 0 @@ -331,10 +372,20 @@ class ShellModel(gobject.GObject): self._activities = [] self._active_activity = None self._tabbing_activity = None - self._pservice = presenceservice.get_instance() + self._launchers = {} self._screen.toggle_showing_desktop(True) + def get_launcher(self, activity_id): + return self._launchers.get(str(activity_id)) + + def register_launcher(self, activity_id, launcher): + self._launchers[activity_id] = launcher + + def unregister_launcher(self, activity_id): + if activity_id in self._launchers: + del self._launchers[activity_id] + def _update_zoom_level(self, window): if window.get_window_type() == wnck.WINDOW_DIALOG: return @@ -426,7 +477,7 @@ class ShellModel(gobject.GObject): def set_tabbing_activity(self, activity): """Sets the activity that is currently highlighted during tabbing""" self._tabbing_activity = activity - self.emit("tabbing-activity-changed", self._tabbing_activity) + self.emit('tabbing-activity-changed', self._tabbing_activity) def _set_active_activity(self, home_activity): if self._active_activity == home_activity: @@ -454,6 +505,21 @@ class ShellModel(gobject.GObject): return self._activities.index(obj) def _window_opened_cb(self, screen, window): + """Handle the callback for the 'window opened' event. + + Most activities will register 2 windows during + their lifetime: the launcher window, and the 'main' + app window. + + When the main window appears, we send a signal to + the launcher window to close. + + Some activities (notably non-native apps) open several + windows during their lifetime, switching from one to + the next as the 'main' window. We use a stack to track + them. + + """ if window.get_window_type() == wnck.WINDOW_NORMAL: home_activity = None @@ -476,31 +542,36 @@ class ShellModel(gobject.GObject): window.maximize() if not home_activity: + logging.debug('first window registered for %s' % activity_id) home_activity = Activity(activity_info, activity_id, window) self._add_activity(home_activity) else: - home_activity.set_window(window) - - if wm.get_sugar_window_type(window) != 'launcher': - home_activity.props.launching = False - if not home_activity.is_journal(): - self.emit('launch-completed', home_activity) + logging.debug('window registered for %s' % activity_id) + home_activity.add_window(window) + if wm.get_sugar_window_type(window) != 'launcher' \ + and home_activity.get_launch_status() == Activity.LAUNCHING: + self.emit('launch-completed', home_activity) startup_time = time.time() - home_activity.get_launch_time() - logging.debug('%s launched in %f seconds.', - home_activity.get_type(), startup_time) + logging.debug('%s launched in %f seconds.' % + (activity_id, startup_time)) if self._active_activity is None: self._set_active_activity(home_activity) def _window_closed_cb(self, screen, window): if window.get_window_type() == wnck.WINDOW_NORMAL: - if self._get_activity_by_xid(window.get_xid()) is not None: - self._remove_activity_by_xid(window.get_xid()) + xid = window.get_xid() + activity = self._get_activity_by_xid(xid) + if activity is not None: + activity.remove_window_by_xid(xid) + if activity.get_window() is None: + logging.debug('last window gone - remove activity %s' % activity) + self._remove_activity(activity) def _get_activity_by_xid(self, xid): for home_activity in self._activities: - if home_activity.get_xid() == xid: + if home_activity.has_xid(xid): return home_activity return None @@ -545,13 +616,6 @@ class ShellModel(gobject.GObject): self.emit('activity-removed', home_activity) self._activities.remove(home_activity) - def _remove_activity_by_xid(self, xid): - home_activity = self._get_activity_by_xid(xid) - if home_activity: - self._remove_activity(home_activity) - else: - logging.error('Model for window %d does not exist.', xid) - def notify_launch(self, activity_id, service_name): registry = get_registry() activity_info = registry.get_bundle(service_name) @@ -560,7 +624,6 @@ class ShellModel(gobject.GObject): " was not found in the bundle registry." % service_name) home_activity = Activity(activity_info, activity_id) - home_activity.props.launching = True self._add_activity(home_activity) self._set_active_activity(home_activity) @@ -575,11 +638,12 @@ class ShellModel(gobject.GObject): def notify_launch_failed(self, activity_id): home_activity = self.get_activity_by_id(activity_id) if home_activity: - logging.debug("Activity %s (%s) launch failed", activity_id, + logging.debug('Activity %s (%s) launch failed', activity_id, home_activity.get_type()) - if home_activity.props.launching: + if self.get_launcher(activity_id) is not None: self.emit('launch-failed', home_activity) else: + # activity sent failure notification after closing launcher self._remove_activity(home_activity) else: logging.error('Model for activity id %s does not exist.', @@ -592,18 +656,15 @@ class ShellModel(gobject.GObject): logging.debug('Activity %s has been closed already.', activity_id) return False - if home_activity.props.launching: + if self.get_launcher(activity_id) is not None: logging.debug('Activity %s still launching, assuming it failed.', activity_id) self.notify_launch_failed(activity_id) return False -_model = None - def get_model(): global _model if _model is None: _model = ShellModel() return _model - |