[Sugar-devel] [PATCH] shell: handle activities that cycle through windows dlo#10695

martin.langhoff at gmail.com martin.langhoff at gmail.com
Thu Feb 17 13:22:37 EST 2011


From: Martin Langhoff <martin at laptop.org>

Now activities have a stack of windows (Activity._windows). The
lowest still-valid window in the stack is considered the main window.

When a window is closed, the shell finds what activity had that window,
tells it to remove it from its stack. If that was the last window
in the activities' stack, the activity is marked as closed.

With this patch, activities can switch from one window to another
safely, without sugar shell losing track of them. Activities based
on AdobeAIR have been seen to switch windows (perhaps when they navigate
from one SWF to another).

This replaces the one-window = one-activity model, where the moment
an activity closed one window (perhaps one of many) it was considered
closed by the shell.
---
Reworked based on Simon's review -- m
---

 src/jarabe/model/shell.py |  107 ++++++++++++++++++++++++++++++---------------
 1 files changed, 71 insertions(+), 36 deletions(-)

diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py
index 661e370..63f6173 100644
--- a/src/jarabe/model/shell.py
+++ b/src/jarabe/model/shell.py
@@ -62,11 +62,12 @@ 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
@@ -74,7 +75,7 @@ class Activity(gobject.GObject):
         self._launch_status = Activity.LAUNCHING
 
         if window is not None:
-            self.set_window(window)
+            self.add_window(window)
 
         self._retrieve_service()
 
@@ -96,15 +97,19 @@ class Activity(gobject.GObject):
 
     launch_status = gobject.property(getter=get_launch_status)
 
-    def set_window(self, window):
-        """Set the window for the activity
-
-        We allow resetting the window for an activity so that we
-        can replace the launcher once we get its real window.
-        """
+    def add_window(self, window):
+        """Add a window to the windows stack."""
         if not window:
             raise ValueError('window must be valid')
-        self._window = window
+        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
@@ -118,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 ''
 
@@ -171,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"""
@@ -211,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"""
@@ -230,8 +250,8 @@ 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 _get_service_name(self):
@@ -485,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
 
@@ -507,29 +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)
+                logging.debug('window registered for %s' % activity_id)
+                home_activity.add_window(window)
 
-            if wm.get_sugar_window_type(window) != 'launcher':
+            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
 
@@ -574,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)
-- 
1.7.3.4



More information about the Sugar-devel mailing list