Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/jarabe/model/shell.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/jarabe/model/shell.py')
-rw-r--r--src/jarabe/model/shell.py255
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
-