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.py165
1 files changed, 97 insertions, 68 deletions
diff --git a/src/jarabe/model/shell.py b/src/jarabe/model/shell.py
index db0e050..63f6173 100644
--- a/src/jarabe/model/shell.py
+++ b/src/jarabe/model/shell.py
@@ -31,9 +31,11 @@ from sugar.graphics.xocolor import XoColor
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"
+_SERVICE_NAME = 'org.laptop.Activity'
+_SERVICE_PATH = '/org/laptop/Activity'
+_SERVICE_INTERFACE = 'org.laptop.Activity'
+
+_model = None
class Activity(gobject.GObject):
@@ -60,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
@@ -72,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()
@@ -81,8 +84,8 @@ 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')
self._launch_completed_hid = get_model().connect('launch-completed',
self.__launch_completed_cb)
@@ -94,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
+ 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
@@ -116,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 ''
@@ -157,7 +164,7 @@ class Activity(gobject.GObject):
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
@@ -169,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"""
@@ -209,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"""
@@ -228,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):
@@ -245,7 +267,7 @@ 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
@@ -275,7 +297,7 @@ 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)
@@ -310,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
@@ -460,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:
@@ -488,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
@@ -510,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
@@ -577,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)
@@ -606,7 +638,7 @@ 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 self.get_launcher(activity_id) is not None:
self.emit('launch-failed', home_activity)
@@ -631,11 +663,8 @@ class ShellModel(gobject.GObject):
return False
-_model = None
-
def get_model():
global _model
if _model is None:
_model = ShellModel()
return _model
-