diff options
author | Marco Pesenti Gritti <marco@localhost.localdomain> | 2008-02-04 22:36:12 (GMT) |
---|---|---|
committer | Marco Pesenti Gritti <marco@localhost.localdomain> | 2008-02-04 22:36:12 (GMT) |
commit | ae5ce06ccb1f604fa1e4eaeb16d9ba8122b4923d (patch) | |
tree | be480c3bef999ace5c11d8faee68cff99fa20dc3 /src/model | |
parent | 652df4bf518976132eda1e9fc6b3b06f6ec4ff04 (diff) |
Refactor directory structure a bit, preliminary to the
library split-out.
Diffstat (limited to 'src/model')
-rw-r--r-- | src/model/BuddyModel.py | 164 | ||||
-rw-r--r-- | src/model/Friends.py | 114 | ||||
-rw-r--r-- | src/model/Invites.py | 72 | ||||
-rw-r--r-- | src/model/Makefile.am | 14 | ||||
-rw-r--r-- | src/model/MeshModel.py | 235 | ||||
-rw-r--r-- | src/model/Owner.py | 87 | ||||
-rw-r--r-- | src/model/__init__.py | 16 | ||||
-rw-r--r-- | src/model/accesspointmodel.py | 81 | ||||
-rw-r--r-- | src/model/devices/Makefile.am | 8 | ||||
-rw-r--r-- | src/model/devices/__init__.py | 16 | ||||
-rw-r--r-- | src/model/devices/battery.py | 96 | ||||
-rw-r--r-- | src/model/devices/device.py | 45 | ||||
-rw-r--r-- | src/model/devices/devicesmodel.py | 136 | ||||
-rw-r--r-- | src/model/devices/network/Makefile.am | 6 | ||||
-rw-r--r-- | src/model/devices/network/__init__.py | 16 | ||||
-rw-r--r-- | src/model/devices/network/mesh.py | 75 | ||||
-rw-r--r-- | src/model/devices/network/wired.py | 28 | ||||
-rw-r--r-- | src/model/devices/network/wireless.py | 96 | ||||
-rw-r--r-- | src/model/homeactivity.py | 215 | ||||
-rw-r--r-- | src/model/homemodel.py | 283 | ||||
-rw-r--r-- | src/model/shellmodel.py | 112 |
21 files changed, 1915 insertions, 0 deletions
diff --git a/src/model/BuddyModel.py b/src/model/BuddyModel.py new file mode 100644 index 0000000..11c6567 --- /dev/null +++ b/src/model/BuddyModel.py @@ -0,0 +1,164 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +from sugar.presence import presenceservice +from sugar.graphics.xocolor import XoColor +import gobject + +_NOT_PRESENT_COLOR = "#888888,#BBBBBB" + +class BuddyModel(gobject.GObject): + __gsignals__ = { + 'appeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'disappeared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])), + 'nick-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'color-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'icon-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([])), + 'current-activity-changed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + def __init__(self, key=None, buddy=None, nick=None): + if (key and buddy) or (not key and not buddy): + raise RuntimeError("Must specify only _one_ of key or buddy.") + + gobject.GObject.__init__(self) + + self._ba_handler = None + self._pc_handler = None + self._dis_handler = None + self._bic_handler = None + self._cac_handler = None + + self._pservice = presenceservice.get_instance() + + self._buddy = None + + if not buddy: + self._key = key + # connect to the PS's buddy-appeared signal and + # wait for the buddy to appear + self._ba_handler = self._pservice.connect('buddy-appeared', + self._buddy_appeared_cb) + # Set color to 'inactive'/'disconnected' + self._set_color_from_string(_NOT_PRESENT_COLOR) + self._nick = nick + + self._pservice.get_buddies_async(reply_handler=self._get_buddies_cb) + else: + self._update_buddy(buddy) + + def _get_buddies_cb(self, list): + buddy = None + for iter_buddy in list: + if iter_buddy.props.key == self._key: + buddy = iter_buddy + break + + if buddy: + if self._ba_handler: + # Once we have the buddy, we no longer need to + # monitor buddy-appeared events + self._pservice.disconnect(self._ba_handler) + self._ba_handler = None + + self._update_buddy(buddy) + + def _set_color_from_string(self, color_string): + self._color = XoColor(color_string) + + def get_key(self): + return self._key + + def get_nick(self): + return self._nick + + def get_color(self): + return self._color + + def get_buddy(self): + return self._buddy + + def is_owner(self): + if not self._buddy: + return False + return self._buddy.props.owner + + def is_present(self): + if self._buddy: + return True + return False + + def get_current_activity(self): + if self._buddy: + return self._buddy.props.current_activity + return None + + def _update_buddy(self, buddy): + if not buddy: + raise ValueError("Buddy cannot be None.") + + self._buddy = buddy + self._key = self._buddy.props.key + self._nick = self._buddy.props.nick + self._set_color_from_string(self._buddy.props.color) + + self._pc_handler = self._buddy.connect('property-changed', self._buddy_property_changed_cb) + self._bic_handler = self._buddy.connect('icon-changed', self._buddy_icon_changed_cb) + + def _buddy_appeared_cb(self, pservice, buddy): + if self._buddy or buddy.props.key != self._key: + return + + if self._ba_handler: + # Once we have the buddy, we no longer need to + # monitor buddy-appeared events + self._pservice.disconnect(self._ba_handler) + self._ba_handler = None + + self._update_buddy(buddy) + self.emit('appeared') + + def _buddy_property_changed_cb(self, buddy, keys): + if not self._buddy: + return + if 'color' in keys: + self._set_color_from_string(self._buddy.props.color) + self.emit('color-changed', self.get_color()) + if 'current-activity' in keys: + self.emit('current-activity-changed', buddy.props.current_activity) + if 'nick' in keys: + self._nick = self._buddy.props.nick + self.emit('nick-changed', self.get_nick()) + + def _buddy_disappeared_cb(self, buddy): + if buddy != self._buddy: + return + self._buddy.disconnect(self._pc_handler) + self._buddy.disconnect(self._dis_handler) + self._buddy.disconnect(self._bic_handler) + self._buddy.disconnect(self._cac_handler) + self._set_color_from_string(_NOT_PRESENT_COLOR) + self.emit('disappeared') + self._buddy = None + + def _buddy_icon_changed_cb(self, buddy): + self.emit('icon-changed') diff --git a/src/model/Friends.py b/src/model/Friends.py new file mode 100644 index 0000000..6fc3e97 --- /dev/null +++ b/src/model/Friends.py @@ -0,0 +1,114 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import dbus +import os +from ConfigParser import ConfigParser + +import gobject + +from model.BuddyModel import BuddyModel +from sugar import env +import logging + +class Friends(gobject.GObject): + __gsignals__ = { + 'friend-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + 'friend-removed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([str])), + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._friends = {} + self._path = os.path.join(env.get_profile_path(), 'friends') + + self.load() + + def has_buddy(self, buddy): + return self._friends.has_key(buddy.get_key()) + + def add_friend(self, buddy_info): + self._friends[buddy_info.get_key()] = buddy_info + self.emit('friend-added', buddy_info) + + def make_friend(self, buddy): + if not self.has_buddy(buddy): + self.add_friend(buddy) + self.save() + + def remove(self, buddy_info): + del self._friends[buddy_info.get_key()] + self.save() + self.emit('friend-removed', buddy_info.get_key()) + + def __iter__(self): + return self._friends.values().__iter__() + + def load(self): + cp = ConfigParser() + + try: + success = cp.read([self._path]) + if success: + for key in cp.sections(): + # HACK: don't screw up on old friends files + if len(key) < 20: + continue + buddy = BuddyModel(key=key, nick=cp.get(key, 'nick')) + self.add_friend(buddy) + except Exception, exc: + logging.error("Error parsing friends file: %s" % exc) + + def save(self): + cp = ConfigParser() + + for friend in self: + section = friend.get_key() + cp.add_section(section) + cp.set(section, 'nick', friend.get_nick()) + cp.set(section, 'color', friend.get_color().to_string()) + + fileobject = open(self._path, 'w') + cp.write(fileobject) + fileobject.close() + + self._sync_friends() + + def _sync_friends(self): + # XXX: temporary hack + # remove this when the shell service has a D-Bus API for buddies + + def friends_synced(): + pass + + def friends_synced_error(e): + logging.error("Error asking presence service to sync friends: %s" + % e) + + keys = [] + for friend in self: + keys.append(friend.get_key()) + + bus = dbus.SessionBus() + ps = bus.get_object('org.laptop.Sugar.Presence', + '/org/laptop/Sugar/Presence') + psi = dbus.Interface(ps, 'org.laptop.Sugar.Presence') + psi.SyncFriends(keys, + reply_handler=friends_synced, + error_handler=friends_synced_error) diff --git a/src/model/Invites.py b/src/model/Invites.py new file mode 100644 index 0000000..9ffab44 --- /dev/null +++ b/src/model/Invites.py @@ -0,0 +1,72 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +from sugar.presence import presenceservice + +class Invite: + def __init__(self, issuer, bundle_id, activity_id): + self._issuer = issuer + self._activity_id = activity_id + self._bundle_id = bundle_id + + def get_activity_id(self): + return self._activity_id + + def get_bundle_id(self): + return self._bundle_id + +class Invites(gobject.GObject): + __gsignals__ = { + 'invite-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + 'invite-removed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([object])), + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._dict = {} + + ps = presenceservice.get_instance() + owner = ps.get_owner() + owner.connect('joined-activity', self._owner_joined_cb) + + def add_invite(self, issuer, bundle_id, activity_id): + if activity_id in self._dict: + # there is no point to add more than one time + # an invite for the same activity + return + + invite = Invite(issuer, bundle_id, activity_id) + self._dict[activity_id] = invite + self.emit('invite-added', invite) + + def remove_invite(self, invite): + self._dict.pop(invite.get_activity_id()) + self.emit('invite-removed', invite) + + def remove_activity(self, activity_id): + invite = self._dict.get(activity_id) + if invite is not None: + self.remove_invite(invite) + + def _owner_joined_cb(self, owner, activity): + self.remove_activity(activity.props.id) + + def __iter__(self): + return self._dict.values().__iter__() diff --git a/src/model/Makefile.am b/src/model/Makefile.am new file mode 100644 index 0000000..0b7d14c --- /dev/null +++ b/src/model/Makefile.am @@ -0,0 +1,14 @@ +SUBDIRS = devices + +sugardir = $(pkgdatadir)/shell/model +sugar_PYTHON = \ + __init__.py \ + accesspointmodel.py \ + BuddyModel.py \ + Friends.py \ + Invites.py \ + Owner.py \ + MeshModel.py \ + shellmodel.py \ + homeactivity.py \ + homemodel.py diff --git a/src/model/MeshModel.py b/src/model/MeshModel.py new file mode 100644 index 0000000..da5b3c2 --- /dev/null +++ b/src/model/MeshModel.py @@ -0,0 +1,235 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject + +from sugar.graphics.xocolor import XoColor +from sugar.presence import presenceservice +from sugar import activity + +from model.BuddyModel import BuddyModel +from model.accesspointmodel import AccessPointModel +from hardware import hardwaremanager +from hardware import nmclient + +class ActivityModel: + def __init__(self, activity, bundle): + self.activity = activity + self.bundle = bundle + + def get_id(self): + return self.activity.props.id + + def get_icon_name(self): + return self.bundle.icon + + def get_color(self): + return XoColor(self.activity.props.color) + + def get_bundle_id(self): + return self.bundle.bundle_id + +class MeshModel(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])), + 'buddy-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'buddy-moved': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT, + gobject.TYPE_PYOBJECT])), + 'buddy-removed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'access-point-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'access-point-removed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'mesh-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'mesh-removed': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([])) + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._activities = {} + self._buddies = {} + self._access_points = {} + self._mesh = None + + self._pservice = presenceservice.get_instance() + self._pservice.connect("activity-appeared", + self._activity_appeared_cb) + self._pservice.connect('activity-disappeared', + self._activity_disappeared_cb) + self._pservice.connect("buddy-appeared", + self._buddy_appeared_cb) + self._pservice.connect("buddy-disappeared", + self._buddy_disappeared_cb) + + # Add any buddies the PS knows about already + self._pservice.get_buddies_async(reply_handler=self._get_buddies_cb) + + self._pservice.get_activities_async(reply_handler=self._get_activities_cb) + + network_manager = hardwaremanager.get_network_manager() + if network_manager: + for nm_device in network_manager.get_devices(): + self._add_network_device(nm_device) + network_manager.connect('device-added', + self._nm_device_added_cb) + network_manager.connect('device-removed', + self._nm_device_removed_cb) + + def _get_buddies_cb(self, list): + for buddy in list: + self._buddy_appeared_cb(self._pservice, buddy) + + def _get_activities_cb(self, list): + for activity in list: + self._check_activity(activity) + + def _nm_device_added_cb(self, manager, nm_device): + self._add_network_device(nm_device) + + def _nm_device_removed_cb(self, manager, nm_device): + self._remove_network_device(nm_device) + + def _nm_network_appeared_cb(self, nm_device, nm_network): + self._add_access_point(nm_device, nm_network) + + def _nm_network_disappeared_cb(self, nm_device, nm_network): + if self._access_points.has_key(nm_network.get_op()): + ap = self._access_points[nm_network.get_op()] + self._remove_access_point(ap) + + def _add_network_device(self, nm_device): + dtype = nm_device.get_type() + if dtype == nmclient.DEVICE_TYPE_802_11_WIRELESS: + for nm_network in nm_device.get_networks(): + self._add_access_point(nm_device, nm_network) + + nm_device.connect('network-appeared', + self._nm_network_appeared_cb) + nm_device.connect('network-disappeared', + self._nm_network_disappeared_cb) + elif dtype == nmclient.DEVICE_TYPE_802_11_MESH_OLPC: + self._mesh = nm_device + self.emit('mesh-added', self._mesh) + + def _remove_network_device(self, nm_device): + if nm_device == self._mesh: + self._mesh = None + self.emit('mesh-removed') + elif nm_device.get_type() == nmclient.DEVICE_TYPE_802_11_WIRELESS: + aplist = self._access_points.values() + for ap in aplist: + if ap.get_nm_device() == nm_device: + self._remove_access_point(ap) + + def _add_access_point(self, nm_device, nm_network): + model = AccessPointModel(nm_device, nm_network) + self._access_points[model.get_id()] = model + self.emit('access-point-added', model) + + def _remove_access_point(self, ap): + if not self._access_points.has_key(ap.get_id()): + return + self.emit('access-point-removed', ap) + del self._access_points[ap.get_id()] + + def get_mesh(self): + return self._mesh + + def get_access_points(self): + return self._access_points.values() + + def get_activities(self): + return self._activities.values() + + def get_buddies(self): + return self._buddies.values() + + def _buddy_activity_changed_cb(self, model, cur_activity): + if not self._buddies.has_key(model.get_key()): + return + if cur_activity and self._activities.has_key(cur_activity.props.id): + activity_model = self._activities[cur_activity.props.id] + self.emit('buddy-moved', model, activity_model) + else: + self.emit('buddy-moved', model, None) + + def _buddy_appeared_cb(self, pservice, buddy): + if self._buddies.has_key(buddy.props.key): + return + + model = BuddyModel(buddy=buddy) + model.connect('current-activity-changed', + self._buddy_activity_changed_cb) + self._buddies[buddy.props.key] = model + self.emit('buddy-added', model) + + cur_activity = buddy.props.current_activity + if cur_activity: + self._buddy_activity_changed_cb(model, cur_activity) + + def _buddy_disappeared_cb(self, pservice, buddy): + if not self._buddies.has_key(buddy.props.key): + return + self.emit('buddy-removed', self._buddies[buddy.props.key]) + del self._buddies[buddy.props.key] + + def _activity_appeared_cb(self, pservice, activity): + self._check_activity(activity) + + def _check_activity(self, presence_activity): + registry = activity.get_registry() + bundle = registry.get_activity(presence_activity.props.type) + if not bundle: + return + if self.has_activity(presence_activity.props.id): + return + self.add_activity(bundle, presence_activity) + + def has_activity(self, activity_id): + return self._activities.has_key(activity_id) + + def get_activity(self, activity_id): + if self.has_activity(activity_id): + return self._activities[activity_id] + else: + return None + + def add_activity(self, bundle, activity): + model = ActivityModel(activity, bundle) + self._activities[model.get_id()] = model + self.emit('activity-added', model) + + for buddy in self._pservice.get_buddies(): + cur_activity = buddy.props.current_activity + key = buddy.props.key + if cur_activity == activity and self._buddies.has_key(key): + buddy_model = self._buddies[key] + self.emit('buddy-moved', buddy_model, model) + + def _activity_disappeared_cb(self, pservice, activity): + if self._activities.has_key(activity.props.id): + activity_model = self._activities[activity.props.id] + self.emit('activity-removed', activity_model) + del self._activities[activity.props.id] diff --git a/src/model/Owner.py b/src/model/Owner.py new file mode 100644 index 0000000..b06b391 --- /dev/null +++ b/src/model/Owner.py @@ -0,0 +1,87 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +import os +import random +import base64 +import time +import logging +import dbus + +from sugar import env +from sugar import profile +from sugar.presence import presenceservice +from sugar import util +from model.Invites import Invites + +class ShellOwner(gobject.GObject): + __gtype_name__ = "ShellOwner" + + __gsignals__ = { + 'nick-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_STRING])), + 'color-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'icon-changed' : (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + """Class representing the owner of this machine/instance. This class + runs in the shell and serves up the buddy icon and other stuff. It's the + server portion of the Owner, paired with the client portion in Buddy.py.""" + def __init__(self): + gobject.GObject.__init__(self) + + self._nick = profile.get_nick_name() + + self._icon = None + self._icon_hash = "" + icon = os.path.join(env.get_profile_path(), "buddy-icon.jpg") + if not os.path.exists(icon): + raise RuntimeError("missing buddy icon") + + fd = open(icon, "r") + self._icon = fd.read() + fd.close() + if not self._icon: + raise RuntimeError("invalid buddy icon") + + # Get the icon's hash + import md5 + digest = md5.new(self._icon).digest() + self._icon_hash = util.printable_hash(digest) + + self._pservice = presenceservice.get_instance() + self._pservice.connect('activity-invitation', + self._activity_invitation_cb) + self._pservice.connect('activity-disappeared', + self._activity_disappeared_cb) + + self._invites = Invites() + + def get_invites(self): + return self._invites + + def get_nick(self): + return self._nick + + def _activity_invitation_cb(self, pservice, activity, buddy, message): + self._invites.add_invite(buddy, activity.props.type, + activity.props.id) + + def _activity_disappeared_cb(self, pservice, activity): + self._invites.remove_activity(activity.props.id) diff --git a/src/model/__init__.py b/src/model/__init__.py new file mode 100644 index 0000000..a9dd95a --- /dev/null +++ b/src/model/__init__.py @@ -0,0 +1,16 @@ +# Copyright (C) 2006-2007, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + diff --git a/src/model/accesspointmodel.py b/src/model/accesspointmodel.py new file mode 100644 index 0000000..1d4d6cb --- /dev/null +++ b/src/model/accesspointmodel.py @@ -0,0 +1,81 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +import sys + +from hardware import nmclient + +STATE_CONNECTING = 0 +STATE_CONNECTED = 1 +STATE_NOTCONNECTED = 2 + +_nm_state_to_state = { + nmclient.NETWORK_STATE_CONNECTED : STATE_CONNECTED, + nmclient.NETWORK_STATE_CONNECTING : STATE_CONNECTING, + nmclient.NETWORK_STATE_NOTCONNECTED : STATE_NOTCONNECTED +} + +class AccessPointModel(gobject.GObject): + __gproperties__ = { + 'name' : (str, None, None, None, + gobject.PARAM_READABLE), + 'strength' : (int, None, None, 0, 100, 0, + gobject.PARAM_READABLE), + 'state' : (int, None, None, STATE_CONNECTING, + STATE_NOTCONNECTED, 0, gobject.PARAM_READABLE), + 'capabilities' : (int, None, None, 0, 0x7FFFFFFF, 0, + gobject.PARAM_READABLE), + 'mode' : (int, None, None, 0, 6, 0, gobject.PARAM_READABLE) + } + + def __init__(self, nm_device, nm_network): + gobject.GObject.__init__(self) + self._nm_network = nm_network + self._nm_device = nm_device + + self._nm_network.connect('strength-changed', + self._strength_changed_cb) + self._nm_network.connect('state-changed', + self._state_changed_cb) + + def _strength_changed_cb(self, nm_network): + self.notify('strength') + + def _state_changed_cb(self, nm_network): + self.notify('state') + + def get_id(self): + return self._nm_network.get_op() + + def get_nm_device(self): + return self._nm_device + + def get_nm_network(self): + return self._nm_network + + def do_get_property(self, pspec): + if pspec.name == 'strength': + return self._nm_network.get_strength() + elif pspec.name == 'name': + return self._nm_network.get_ssid() + elif pspec.name == 'state': + nm_state = self._nm_network.get_state() + return _nm_state_to_state[nm_state] + elif pspec.name == 'capabilities': + return self._nm_network.get_caps() + elif pspec.name == 'mode': + return self._nm_network.get_mode() diff --git a/src/model/devices/Makefile.am b/src/model/devices/Makefile.am new file mode 100644 index 0000000..5440eeb --- /dev/null +++ b/src/model/devices/Makefile.am @@ -0,0 +1,8 @@ +SUBDIRS = network + +sugardir = $(pkgdatadir)/shell/model/devices +sugar_PYTHON = \ + __init__.py \ + device.py \ + devicesmodel.py \ + battery.py diff --git a/src/model/devices/__init__.py b/src/model/devices/__init__.py new file mode 100644 index 0000000..a9dd95a --- /dev/null +++ b/src/model/devices/__init__.py @@ -0,0 +1,16 @@ +# Copyright (C) 2006-2007, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + diff --git a/src/model/devices/battery.py b/src/model/devices/battery.py new file mode 100644 index 0000000..853d00e --- /dev/null +++ b/src/model/devices/battery.py @@ -0,0 +1,96 @@ +# Copyright (C) 2006-2007, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +import gobject +import dbus + +from model.devices import device + +_LEVEL_PROP = 'battery.charge_level.percentage' +_CHARGING_PROP = 'battery.rechargeable.is_charging' +_DISCHARGING_PROP = 'battery.rechargeable.is_discharging' + +class Device(device.Device): + __gproperties__ = { + 'level' : (int, None, None, 0, 100, 0, + gobject.PARAM_READABLE), + 'charging' : (bool, None, None, False, + gobject.PARAM_READABLE), + 'discharging' : (bool, None, None, False, + gobject.PARAM_READABLE) + } + + def __init__(self, udi): + device.Device.__init__(self, udi) + + bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) + proxy = bus.get_object('org.freedesktop.Hal', udi) + self._battery = dbus.Interface(proxy, 'org.freedesktop.Hal.Device') + bus.add_signal_receiver(self._battery_changed, + 'PropertyModified', + 'org.freedesktop.Hal.Device', + 'org.freedesktop.Hal', + udi) + + self._level = self._get_level() + self._charging = self._get_charging() + self._discharging = self._get_discharging() + + def _get_level(self): + try: + return self._battery.GetProperty(_LEVEL_PROP) + except dbus.DBusException: + logging.error('Cannot access %s' % _LEVEL_PROP) + return 0 + + def _get_charging(self): + try: + return self._battery.GetProperty(_CHARGING_PROP) + except dbus.DBusException: + logging.error('Cannot access %s' % _CHARGING_PROP) + return False + + def _get_discharging(self): + try: + return self._battery.GetProperty(_DISCHARGING_PROP) + except dbus.DBusException: + logging.error('Cannot access %s' % _DISCHARGING_PROP) + return False + + def do_get_property(self, pspec): + if pspec.name == 'level': + return self._level + if pspec.name == 'charging': + return self._charging + if pspec.name == 'discharging': + return self._discharging + + def get_type(self): + return 'battery' + + def _battery_changed(self, num_changes, changes_list): + for change in changes_list: + if change[0] == _LEVEL_PROP: + self._level = self._get_level() + self.notify('level') + elif change[0] == _CHARGING_PROP: + self._charging = self._get_charging() + self.notify('charging') + elif change[0] == _DISCHARGING_PROP: + self._discharging = self._get_discharging() + self.notify('discharging') diff --git a/src/model/devices/device.py b/src/model/devices/device.py new file mode 100644 index 0000000..d7105b5 --- /dev/null +++ b/src/model/devices/device.py @@ -0,0 +1,45 @@ +# +# Copyright (C) 2007, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject +from hardware import nmclient + +from sugar import util + +STATE_ACTIVATING = 0 +STATE_ACTIVATED = 1 +STATE_INACTIVE = 2 + +_nm_state_to_state = { + nmclient.DEVICE_STATE_ACTIVATING : STATE_ACTIVATING, + nmclient.DEVICE_STATE_ACTIVATED : STATE_ACTIVATED, + nmclient.DEVICE_STATE_INACTIVE : STATE_INACTIVE +} + +class Device(gobject.GObject): + def __init__(self, device_id=None): + gobject.GObject.__init__(self) + if device_id: + self._id = device_id + else: + self._id = util.unique_id() + + def get_type(self): + return 'unknown' + + def get_id(self): + return self._id diff --git a/src/model/devices/devicesmodel.py b/src/model/devices/devicesmodel.py new file mode 100644 index 0000000..fab9fa4 --- /dev/null +++ b/src/model/devices/devicesmodel.py @@ -0,0 +1,136 @@ +# +# Copyright (C) 2007, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging +import gobject +import dbus + +from model.devices import device +from model.devices.network import wired +from model.devices.network import wireless +from model.devices.network import mesh +from model.devices import battery +from hardware import hardwaremanager +from hardware import nmclient + +class DevicesModel(gobject.GObject): + __gsignals__ = { + 'device-appeared' : (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'device-disappeared': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._devices = {} + self._sigids = {} + + self._observe_hal_manager() + self._observe_network_manager() + + def _observe_hal_manager(self): + bus = dbus.Bus(dbus.Bus.TYPE_SYSTEM) + proxy = bus.get_object('org.freedesktop.Hal', + '/org/freedesktop/Hal/Manager') + hal_manager = dbus.Interface(proxy, 'org.freedesktop.Hal.Manager') + + for udi in hal_manager.FindDeviceByCapability('battery'): + self.add_device(battery.Device(udi)) + + def _observe_network_manager(self): + network_manager = hardwaremanager.get_network_manager() + if not network_manager: + return + + for device in network_manager.get_devices(): + self._check_network_device(device) + + network_manager.connect('device-added', + self._network_device_added_cb) + network_manager.connect('device-activating', + self._network_device_activating_cb) + network_manager.connect('device-activated', + self._network_device_activated_cb) + network_manager.connect('device-removed', + self._network_device_removed_cb) + + def _network_device_added_cb(self, network_manager, nm_device): + state = nm_device.get_state() + if state == nmclient.DEVICE_STATE_ACTIVATING \ + or state == nmclient.DEVICE_STATE_ACTIVATED: + self._check_network_device(nm_device) + + def _network_device_activating_cb(self, network_manager, nm_device): + self._check_network_device(nm_device) + + def _network_device_activated_cb(self, network_manager, nm_device): + pass + + def _network_device_removed_cb(self, network_manager, nm_device): + if self._devices.has_key(str(nm_device.get_op())): + self.remove_device(self._get_network_device(nm_device)) + + def _check_network_device(self, nm_device): + if not nm_device.is_valid(): + logging.debug("Device %s not valid" % nm_device.get_op()) + return + + dtype = nm_device.get_type() + if dtype == nmclient.DEVICE_TYPE_802_11_WIRELESS \ + or dtype == nmclient.DEVICE_TYPE_802_11_MESH_OLPC: + self._add_network_device(nm_device) + + def _get_network_device(self, nm_device): + return self._devices[str(nm_device.get_op())] + + def _network_device_state_changed_cb(self, dev, param): + if dev.props.state == device.STATE_INACTIVE: + self.remove_device(dev) + + def _add_network_device(self, nm_device): + if self._devices.has_key(str(nm_device.get_op())): + logging.debug("Tried to add device %s twice" % nm_device.get_op()) + return + + dtype = nm_device.get_type() + if dtype == nmclient.DEVICE_TYPE_802_11_WIRELESS: + dev = wireless.Device(nm_device) + self.add_device(dev) + sigid = dev.connect('notify::state', self._network_device_state_changed_cb) + self._sigids[dev] = sigid + if dtype == nmclient.DEVICE_TYPE_802_11_MESH_OLPC: + dev = mesh.Device(nm_device) + self.add_device(dev) + sigid = dev.connect('notify::state', self._network_device_state_changed_cb) + self._sigids[dev] = sigid + + def __iter__(self): + return iter(self._devices.values()) + + def add_device(self, device): + self._devices[device.get_id()] = device + self.emit('device-appeared', device) + + def remove_device(self, device): + self.emit('device-disappeared', self._devices[device.get_id()]) + device.disconnect(self._sigids[device]) + del self._sigids[device] + del self._devices[device.get_id()] diff --git a/src/model/devices/network/Makefile.am b/src/model/devices/network/Makefile.am new file mode 100644 index 0000000..04074e5 --- /dev/null +++ b/src/model/devices/network/Makefile.am @@ -0,0 +1,6 @@ +sugardir = $(pkgdatadir)/shell/model/devices/network +sugar_PYTHON = \ + __init__.py \ + mesh.py \ + wired.py \ + wireless.py diff --git a/src/model/devices/network/__init__.py b/src/model/devices/network/__init__.py new file mode 100644 index 0000000..a9dd95a --- /dev/null +++ b/src/model/devices/network/__init__.py @@ -0,0 +1,16 @@ +# Copyright (C) 2006-2007, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + diff --git a/src/model/devices/network/mesh.py b/src/model/devices/network/mesh.py new file mode 100644 index 0000000..0152d8a --- /dev/null +++ b/src/model/devices/network/mesh.py @@ -0,0 +1,75 @@ +# +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject + +from model.devices import device +from hardware import nmclient + +class Device(device.Device): + __gproperties__ = { + 'strength' : (int, None, None, 0, 100, 0, + gobject.PARAM_READABLE), + 'state' : (int, None, None, device.STATE_ACTIVATING, + device.STATE_INACTIVE, 0, gobject.PARAM_READABLE), + 'activation-stage': (int, None, None, 0, 7, 0, gobject.PARAM_READABLE), + 'frequency': (float, None, None, 0, 2.72, 0, gobject.PARAM_READABLE), + 'mesh-step': (int, None, None, 0, 4, 0, gobject.PARAM_READABLE), + } + + def __init__(self, nm_device): + device.Device.__init__(self) + self._nm_device = nm_device + + self._nm_device.connect('strength-changed', + self._strength_changed_cb) + self._nm_device.connect('state-changed', + self._state_changed_cb) + self._nm_device.connect('activation-stage-changed', + self._activation_stage_changed_cb) + + def _strength_changed_cb(self, nm_device): + self.notify('strength') + + def _state_changed_cb(self, nm_device): + self.notify('state') + + def _activation_stage_changed_cb(self, nm_device): + self.notify('activation-stage') + + def do_get_property(self, pspec): + if pspec.name == 'strength': + return self._nm_device.get_strength() + elif pspec.name == 'state': + nm_state = self._nm_device.get_state() + return device._nm_state_to_state[nm_state] + elif pspec.name == 'activation-stage': + return self._nm_device.get_activation_stage() + elif pspec.name == 'frequency': + return self._nm_device.get_frequency() + elif pspec.name == 'mesh-step': + return self._nm_device.get_mesh_step() + + def get_type(self): + return 'network.mesh' + + def get_id(self): + return str(self._nm_device.get_op()) + + def get_nm_device(self): + return self._nm_device + diff --git a/src/model/devices/network/wired.py b/src/model/devices/network/wired.py new file mode 100644 index 0000000..66c5206 --- /dev/null +++ b/src/model/devices/network/wired.py @@ -0,0 +1,28 @@ +# Copyright (C) 2006-2007, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +from model.devices import device + +class Device(device.Device): + def __init__(self, nm_device): + device.Device.__init__(self) + self._nm_device = device + + def get_id(self): + return str(self._nm_device.get_op()) + + def get_type(self): + return 'network.wired' diff --git a/src/model/devices/network/wireless.py b/src/model/devices/network/wireless.py new file mode 100644 index 0000000..c45a08e --- /dev/null +++ b/src/model/devices/network/wireless.py @@ -0,0 +1,96 @@ +# +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import gobject + +from model.devices import device +from hardware import nmclient + +def freq_to_channel(freq): + ftoc = { 2.412: 1, 2.417: 2, 2.422: 3, 2.427: 4, + 2.432: 5, 2.437: 6, 2.442: 7, 2.447: 8, + 2.452: 9, 2.457: 10, 2.462: 11, 2.467: 12, + 2.472: 13 + } + return ftoc[freq] + +def channel_to_freq(channel): + ctof = { 1: 2.412, 2: 2.417, 3: 2.422, 4: 2.427, + 5: 2.432, 6: 2.437, 7: 2.442, 8: 2.447, + 9: 2.452, 10: 2.457, 11: 2.462, 12: 2.467, + 13: 2.472 + } + return ctof[channel] + + +class Device(device.Device): + __gproperties__ = { + 'name' : (str, None, None, None, + gobject.PARAM_READABLE), + 'strength' : (int, None, None, 0, 100, 0, + gobject.PARAM_READABLE), + 'state' : (int, None, None, device.STATE_ACTIVATING, + device.STATE_INACTIVE, 0, gobject.PARAM_READABLE), + 'frequency': (float, None, None, 0.0, 9999.99, 0.0, + gobject.PARAM_READABLE) + } + + def __init__(self, nm_device): + device.Device.__init__(self) + self._nm_device = nm_device + + self._nm_device.connect('strength-changed', + self._strength_changed_cb) + self._nm_device.connect('ssid-changed', + self._ssid_changed_cb) + self._nm_device.connect('state-changed', + self._state_changed_cb) + + def _strength_changed_cb(self, nm_device): + self.notify('strength') + + def _ssid_changed_cb(self, nm_device): + self.notify('name') + + def _state_changed_cb(self, nm_device): + self.notify('state') + + def do_get_property(self, pspec): + if pspec.name == 'strength': + return self._nm_device.get_strength() + elif pspec.name == 'name': + import logging + logging.debug('wireless.Device.props.name: %s' % self._nm_device.get_ssid()) + return self._nm_device.get_ssid() + elif pspec.name == 'state': + nm_state = self._nm_device.get_state() + return device._nm_state_to_state[nm_state] + elif pspec.name == 'frequency': + return self._nm_device.get_frequency() + + def get_type(self): + return 'network.wireless' + + def get_id(self): + return str(self._nm_device.get_op()) + + def get_active_network_colors(self): + net = self._nm_device.get_active_network() + if not net: + return (None, None) + return net.get_colors() + diff --git a/src/model/homeactivity.py b/src/model/homeactivity.py new file mode 100644 index 0000000..7365271 --- /dev/null +++ b/src/model/homeactivity.py @@ -0,0 +1,215 @@ +# Copyright (C) 2006-2007 Owen Williams. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import time +import logging + +import gobject +import dbus + +from sugar.graphics.xocolor import XoColor +from sugar.presence import presenceservice +from sugar import profile + +_SERVICE_NAME = "org.laptop.Activity" +_SERVICE_PATH = "/org/laptop/Activity" +_SERVICE_INTERFACE = "org.laptop.Activity" + +class HomeActivity(gobject.GObject): + """Activity which appears in the "Home View" of the Sugar shell + + This class stores the Sugar Shell's metadata regarding a + given activity/application in the system. It interacts with + the sugar.activity.* modules extensively in order to + accomplish its tasks. + """ + + __gtype_name__ = 'SugarHomeActivity' + + __gproperties__ = { + 'launching' : (bool, None, None, False, + gobject.PARAM_READWRITE), + } + + def __init__(self, activity_info, activity_id): + """Initialise the HomeActivity + + activity_info -- sugar.activity.registry.ActivityInfo instance, + provides the information required to actually + create the new instance. This is, in effect, + the "type" of activity being created. + activity_id -- unique identifier for this instance + of the activity type + """ + gobject.GObject.__init__(self) + + self._window = None + self._xid = None + self._pid = None + self._service = None + self._activity_id = activity_id + self._activity_info = activity_info + self._launch_time = time.time() + self._launching = False + + self._retrieve_service() + + if not self._service: + bus = dbus.SessionBus() + bus.add_signal_receiver(self._name_owner_changed_cb, + signal_name="NameOwnerChanged", + dbus_interface="org.freedesktop.DBus") + + def set_window(self, window): + """An activity is 'launched' once we get its window.""" + if self._window or self._xid: + raise RuntimeError("Activity is already launched!") + if not window: + raise ValueError("window must be valid") + + self._window = window + self._xid = window.get_xid() + self._pid = window.get_pid() + + def get_service(self): + """Get the activity service + + Note that non-native Sugar applications will not have + such a service, so the return value will be None in + those cases. + """ + + return self._service + + def get_title(self): + """Retrieve the application's root window's suggested title""" + if self._window: + return self._window.get_name() + else: + return '' + + def get_icon_path(self): + """Retrieve the activity's icon (file) name""" + if self._activity_info: + return self._activity_info.icon + else: + return None + + def get_icon_color(self): + """Retrieve the appropriate icon colour for this activity + + Uses activity_id to index into the PresenceService's + set of activity colours, if the PresenceService does not + have an entry (implying that this is not a Sugar-shared application) + uses the local user's profile.get_color() to determine the + 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: + activity = act + break + + if activity != None: + return XoColor(activity.props.color) + else: + return profile.get_color() + + def get_activity_id(self): + """Retrieve the "activity_id" passed in to our constructor + + This is a "globally likely unique" identifier generated by + sugar.util.unique_id + """ + return self._activity_id + + def get_xid(self): + """Retrieve the X-windows ID of our root window""" + return self._xid + + def get_window(self): + """Retrieve the X-windows root window of this application + + This was stored by the set_window method, which was + called by HomeModel._add_activity, which was called + via a callback that looks for all 'window-opened' + events. + + HomeModel currently uses a dbus service query on the + activity to determine to which HomeActivity the newly + launched window belongs. + """ + return self._window + + def get_type(self): + """Retrieve the activity bundle id for future reference""" + if self._activity_info: + return self._activity_info.bundle_id + else: + return None + + def get_launch_time(self): + """Return the time at which the activity was first launched + + Format is floating-point time.time() value + (seconds since the epoch) + """ + return self._launch_time + + def get_pid(self): + """Returns the activity's PID""" + return self._pid + + def equals(self, activity): + if self._activity_id and activity.get_activity_id(): + return self._activity_id == activity.get_activity_id() + if self._xid and activity.get_xid(): + return self._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 + else: + return None + + def _retrieve_service(self): + if not self._activity_id: + return + + try: + bus = dbus.SessionBus() + proxy = bus.get_object(self._get_service_name(), + _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() diff --git a/src/model/homemodel.py b/src/model/homemodel.py new file mode 100644 index 0000000..44d5417 --- /dev/null +++ b/src/model/homemodel.py @@ -0,0 +1,283 @@ +# Copyright (C) 2006-2007 Owen Williams. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import logging + +import gobject +import wnck +import dbus + +from sugar import wm +from sugar import activity + +from model.homeactivity import HomeActivity + +class HomeModel(gobject.GObject): + """Model of the "Home" view (activity management) + + The HomeModel is basically the point of registration + for all running activities within Sugar. It traps + events that tell the system there is a new activity + being created (generated by the activity factories), + or removed, as well as those which tell us that the + currently focussed activity has changed. + + The HomeModel tracks a set of HomeActivity instances, + which are tracking the window to activity mappings + the activity factories have set up. + """ + __gsignals__ = { + 'activity-added': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])), + 'activity-started': (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])), + 'pending-activity-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, + ([gobject.TYPE_PYOBJECT])) + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._activities = [] + self._active_activity = None + self._pending_activity = None + + screen = wnck.screen_get_default() + screen.connect('window-opened', self._window_opened_cb) + screen.connect('window-closed', self._window_closed_cb) + screen.connect('active-window-changed', + self._active_window_changed_cb) + + def _get_activities_with_window(self): + ret = [] + for i in self._activities: + if i.get_window() is not None: + ret.append(i) + return ret + + def get_previous_activity(self): + activities = self._get_activities_with_window() + i = activities.index(self._pending_activity) + if len(activities) == 0: + return None + elif i - 1 >= 0: + return activities[i - 1] + else: + return activities[len(activities) - 1] + + def get_next_activity(self): + activities = self._get_activities_with_window() + i = activities.index(self._pending_activity) + if len(activities) == 0: + return None + elif i + 1 < len(activities): + return activities[i + 1] + else: + return activities[0] + + def get_pending_activity(self): + """Returns the activity that would be seen in the Activity zoom level + + In the Home (or Neighborhood or Groups) zoom level, this + indicates the activity that would become active if the user + switched to the Activity zoom level. (In the Activity zoom + level, this just returns the currently-active activity.) + Unlike get_active_activity(), this never returns None as long + as there is any activity running. + """ + return self._pending_activity + + def _set_pending_activity(self, home_activity): + if self._pending_activity == home_activity: + return + + self._pending_activity = home_activity + self.emit('pending-activity-changed', self._pending_activity) + + def get_active_activity(self): + """Returns the activity that the user is currently working in + + In the Activity zoom level, this returns the currently-active + activity. In the other zoom levels, it returns the activity + that was most-recently active in the Activity zoom level, or + None if the most-recently-active activity is no longer + running. + """ + return self._active_activity + + def _set_active_activity(self, home_activity): + if self._active_activity == home_activity: + return + + if self._active_activity: + service = self._active_activity.get_service() + if service: + service.SetActive(False, + reply_handler=self._set_active_success, + error_handler=self._set_active_error) + if home_activity: + service = home_activity.get_service() + if service: + service.SetActive(True, + reply_handler=self._set_active_success, + error_handler=self._set_active_error) + + self._active_activity = home_activity + self.emit('active-activity-changed', self._active_activity) + + def __iter__(self): + return iter(self._activities) + + def __len__(self): + return len(self._activities) + + def __getitem__(self, i): + return self._activities[i] + + def index(self, obj): + return self._activities.index(obj) + + def _window_opened_cb(self, screen, window): + if window.get_window_type() == wnck.WINDOW_NORMAL: + home_activity = None + + activity_id = wm.get_activity_id(window) + + service_name = wm.get_bundle_id(window) + if service_name: + registry = activity.get_registry() + activity_info = registry.get_activity(service_name) + else: + activity_info = None + + if activity_id: + home_activity = self._get_activity_by_id(activity_id) + + if not home_activity: + home_activity = HomeActivity(activity_info, activity_id) + self._add_activity(home_activity) + + home_activity.set_window(window) + + home_activity.props.launching = False + self.emit('activity-started', home_activity) + + if self._pending_activity is None: + self._set_pending_activity(home_activity) + + def _window_closed_cb(self, screen, window): + if window.get_window_type() == wnck.WINDOW_NORMAL: + self._remove_activity_by_xid(window.get_xid()) + + def _get_activity_by_xid(self, xid): + for home_activity in self._activities: + if home_activity.get_xid() == xid: + return home_activity + return None + + def _get_activity_by_id(self, activity_id): + for home_activity in self._activities: + if home_activity.get_activity_id() == activity_id: + return home_activity + return None + + def _set_active_success(self): + pass + + def _set_active_error(self, err): + logging.error("set_active() failed: %s" % err) + + def _active_window_changed_cb(self, screen, previous_window=None): + window = screen.get_active_window() + if window is None: + return + + if window.get_window_type() != wnck.WINDOW_DIALOG: + while window.get_transient() is not None: + window = window.get_transient() + + activity = self._get_activity_by_xid(window.get_xid()) + if activity is not None: + self._set_pending_activity(activity) + self._set_active_activity(activity) + + def _add_activity(self, home_activity): + self._activities.append(home_activity) + self.emit('activity-added', home_activity) + + def _remove_activity(self, home_activity): + if home_activity == self._active_activity: + self._set_active_activity(None) + + if home_activity == self._pending_activity: + # Figure out the new _pending_activity + windows = wnck.screen_get_default().get_windows_stacked() + windows.reverse() + for window in windows: + new_activity = self._get_activity_by_xid(window.get_xid()) + if new_activity is not None: + self._set_pending_activity(new_activity) + break + else: + logging.error('No activities are running') + self._set_pending_activity(None) + + 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_activity_launch(self, activity_id, service_name): + registry = activity.get_registry() + activity_info = registry.get_activity(service_name) + if not activity_info: + raise ValueError("Activity service name '%s' was not found in the bundle registry." % service_name) + home_activity = HomeActivity(activity_info, activity_id) + home_activity.props.launching = True + self._add_activity(home_activity) + + # FIXME: better learn about finishing processes by receiving a signal. + # Now just check whether an activity has a window after ~90sec + gobject.timeout_add(90000, self._check_activity_launched, activity_id) + + def notify_activity_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, home_activity.get_type())) + self._remove_activity(home_activity) + else: + logging.error('Model for activity id %s does not exist.' % activity_id) + + def _check_activity_launched(self, activity_id): + home_activity = self._get_activity_by_id(activity_id) + if home_activity and home_activity.props.launching: + logging.debug('Activity %s still launching, assuming it failed...', activity_id) + self.notify_activity_launch_failed(activity_id) + return False diff --git a/src/model/shellmodel.py b/src/model/shellmodel.py new file mode 100644 index 0000000..5462e27 --- /dev/null +++ b/src/model/shellmodel.py @@ -0,0 +1,112 @@ +# Copyright (C) 2006-2007 Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +import os + +import wnck +import gobject + +from sugar.presence import presenceservice +from model.Friends import Friends +from model.MeshModel import MeshModel +from model.homemodel import HomeModel +from model.Owner import ShellOwner +from model.devices.devicesmodel import DevicesModel +from sugar import env + +class ShellModel(gobject.GObject): + STATE_STARTUP = 0 + STATE_RUNNING = 1 + STATE_SHUTDOWN = 2 + + ZOOM_MESH = 0 + ZOOM_FRIENDS = 1 + ZOOM_HOME = 2 + ZOOM_ACTIVITY = 3 + + __gproperties__ = { + 'state' : (int, None, None, + 0, 2, STATE_RUNNING, + gobject.PARAM_READWRITE), + 'zoom-level' : (int, None, None, + 0, 3, ZOOM_HOME, + gobject.PARAM_READABLE) + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._current_activity = None + self._state = self.STATE_RUNNING + self._zoom_level = self.ZOOM_HOME + self._showing_desktop = True + + self._pservice = presenceservice.get_instance() + + self._owner = ShellOwner() + + self._friends = Friends() + self._mesh = MeshModel() + self._home = HomeModel() + self._devices = DevicesModel() + + self._screen = wnck.screen_get_default() + self._screen.connect('showing-desktop-changed', + self._showing_desktop_changed_cb) + + def set_zoom_level(self, level): + self._zoom_level = level + self.notify('zoom-level') + + def get_zoom_level(self): + if self._screen.get_showing_desktop(): + return self._zoom_level + else: + return self.ZOOM_ACTIVITY + + def do_set_property(self, pspec, value): + if pspec.name == 'state': + self._state = value + + def do_get_property(self, pspec): + if pspec.name == 'state': + return self._state + elif pspec.name == 'zoom-level': + return self.get_zoom_level() + + def get_mesh(self): + return self._mesh + + def get_friends(self): + return self._friends + + def get_invites(self): + return self._owner.get_invites() + + def get_home(self): + return self._home + + def get_owner(self): + return self._owner + + def get_devices(self): + return self._devices + + def _showing_desktop_changed_cb(self, screen): + showing_desktop = self._screen.get_showing_desktop() + if self._showing_desktop != showing_desktop: + self._showing_desktop = showing_desktop + self.notify('zoom-level') |