Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Langhoff <martin@laptop.org>2011-02-17 18:22:37 (GMT)
committer Simon Schampijer <simon@schampijer.de>2011-02-17 20:59:34 (GMT)
commitdc8f6ed7852f919fe7123d458706fb82430257e9 (patch)
treecf7b40585f3e3843ad31d15c2006f11e71c7f014
parent7b44b42605a72f99a2285c90542e7cf7629c8752 (diff)
Handle activities that cycle through windows dlo#10695
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. Signed-off-by: Martin Langhoff <martin@laptop.org> Acked-by: Simon Schampijer <simon@laptop.org>
-rw-r--r--src/jarabe/model/shell.py107
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)