diff options
author | Dan Williams <dcbw@localhost.localdomain> | 2006-09-15 20:50:06 (GMT) |
---|---|---|
committer | Dan Williams <dcbw@localhost.localdomain> | 2006-09-15 20:50:06 (GMT) |
commit | 922b7238b9db8307f6b9df9b3a4e36f9a46e81a0 (patch) | |
tree | 90aa62656494be7b5740c43549adbbcfaab49ac8 /shell | |
parent | b39eff33655590565aaac3d514df640f1ad3f2a8 (diff) | |
parent | e79a5f5e989fa062366295c3f236692fc3c889e5 (diff) |
Merge
Diffstat (limited to 'shell')
-rw-r--r-- | shell/ChatController.py | 54 | ||||
-rw-r--r-- | shell/Makefile.am | 14 | ||||
-rw-r--r-- | shell/PresenceService/Activity.py | 171 | ||||
-rw-r--r-- | shell/PresenceService/Buddy.py | 402 | ||||
-rw-r--r-- | shell/PresenceService/Makefile.am | 18 | ||||
-rw-r--r-- | shell/PresenceService/PresenceService.py | 785 | ||||
-rw-r--r-- | shell/PresenceService/Service.py | 488 | ||||
-rw-r--r-- | shell/PresenceService/org.laptop.Presence.service.in | 4 | ||||
-rw-r--r-- | shell/Session.py | 13 | ||||
-rwxr-xr-x | shell/Shell.py | 174 | ||||
-rw-r--r-- | shell/home/FriendsGroup.py | 84 | ||||
-rw-r--r-- | shell/model/BuddyInfo.py | 24 | ||||
-rw-r--r-- | shell/model/Friends.py (renamed from shell/Friends.py) | 51 | ||||
-rw-r--r-- | shell/model/Invites.py (renamed from shell/Invites.py) | 0 | ||||
-rw-r--r-- | shell/model/Makefile.am | 8 | ||||
-rw-r--r-- | shell/model/Owner.py (renamed from shell/Owner.py) | 8 | ||||
-rw-r--r-- | shell/model/ShellModel.py | 64 | ||||
-rw-r--r-- | shell/model/__init__.py (copied from shell/PresenceService/__init__.py) | 0 | ||||
-rwxr-xr-x | shell/sugar-presence-service | 11 | ||||
-rw-r--r-- | shell/view/ActivityHost.py (renamed from shell/ActivityHost.py) | 3 | ||||
-rw-r--r-- | shell/view/BuddyIcon.py | 108 | ||||
-rw-r--r-- | shell/view/BuddyPopup.py (renamed from shell/FriendPopup.py) | 67 | ||||
-rw-r--r-- | shell/view/ConsoleWindow.py (renamed from shell/ConsoleWindow.py) | 0 | ||||
-rw-r--r-- | shell/view/FirstTimeDialog.py (renamed from shell/FirstTimeDialog.py) | 0 | ||||
-rw-r--r-- | shell/view/Makefile.am | 11 | ||||
-rw-r--r-- | shell/view/Shell.py | 102 | ||||
-rw-r--r-- | shell/view/__init__.py (renamed from shell/frame/__init__.py) | 0 | ||||
-rw-r--r-- | shell/view/frame/BottomPanel.py (renamed from shell/frame/BottomPanel.py) | 12 | ||||
-rw-r--r-- | shell/view/frame/Frame.py (renamed from shell/frame/Frame.py) | 18 | ||||
-rw-r--r-- | shell/view/frame/Makefile.am (renamed from shell/frame/Makefile.am) | 2 | ||||
-rw-r--r-- | shell/view/frame/PanelWindow.py (renamed from shell/frame/PanelWindow.py) | 0 | ||||
-rw-r--r-- | shell/view/frame/RightPanel.py (renamed from shell/frame/RightPanel.py) | 21 | ||||
-rw-r--r-- | shell/view/frame/TopPanel.py (renamed from shell/frame/TopPanel.py) | 12 | ||||
-rw-r--r-- | shell/view/frame/__init__.py (renamed from shell/home/__init__.py) | 0 | ||||
-rw-r--r-- | shell/view/home/DonutItem.py (renamed from shell/home/DonutItem.py) | 0 | ||||
-rw-r--r-- | shell/view/home/FriendsGroup.py | 42 | ||||
-rw-r--r-- | shell/view/home/HomeGroup.py (renamed from shell/home/HomeGroup.py) | 14 | ||||
-rw-r--r-- | shell/view/home/HomeWindow.py (renamed from shell/home/HomeWindow.py) | 15 | ||||
-rw-r--r-- | shell/view/home/IconLayout.py (renamed from shell/home/IconLayout.py) | 0 | ||||
-rw-r--r-- | shell/view/home/Makefile.am (renamed from shell/home/Makefile.am) | 2 | ||||
-rw-r--r-- | shell/view/home/MeshGroup.py (renamed from shell/home/MeshGroup.py) | 4 | ||||
-rw-r--r-- | shell/view/home/MyIcon.py (renamed from shell/home/MyIcon.py) | 0 | ||||
-rw-r--r-- | shell/view/home/__init__.py (renamed from shell/PresenceService/__init__.py) | 0 |
43 files changed, 471 insertions, 2335 deletions
diff --git a/shell/ChatController.py b/shell/ChatController.py deleted file mode 100644 index 022d608..0000000 --- a/shell/ChatController.py +++ /dev/null @@ -1,54 +0,0 @@ -import conf -from sugar.chat.BuddyChat import BuddyChat -from sugar.activity import ActivityFactory -from sugar.presence import PresenceService -from sugar.p2p.Stream import Stream -from sugar.chat.Chat import Chat - -class ChatController: - def __init__(self, shell): - self._shell = shell - self._id_to_name = {} - self._name_to_chat = {} - - self._shell.connect('activity-closed', self.__activity_closed_cb) - - def __activity_closed_cb(self, shell, activity): - activity_id = activity.get_id() - if self._id_to_name.has_key(activity_id): - name = self._id_to_name[activity_id] - del self._name_to_chat[name] - del self._id_to_name[activity_id] - - def listen(self): - self._pservice = PresenceService.get_instance() - - self._pservice.register_service_type(BuddyChat.SERVICE_TYPE) - profile = conf.get_profile() - self._service = self._pservice.register_service(profile.get_nick_name(), - BuddyChat.SERVICE_TYPE) - - self._buddy_stream = Stream.new_from_service(self._service) - self._buddy_stream.set_data_listener(self._recv_message) - - def open_chat_activity(self, buddy): - service = buddy.get_service_of_type(BuddyChat.SERVICE_TYPE) - if service: - activity = self._shell.start_activity('com.redhat.Sugar.ChatActivity') - activity.execute('connect', [service.object_path()]) - self._name_to_chat[buddy.get_name()] = activity - self._id_to_name[activity.get_id()] = buddy.get_name() - - def _get_chat_activity(self, buddy): - nick = buddy.get_name() - if not self._name_to_chat.has_key(nick): - self.open_chat_activity(buddy) - return self._name_to_chat[nick] - - def _recv_message(self, address, message): - [nick, msg] = Chat.deserialize_message(message) - buddy = self._pservice.get_buddy_by_name(nick) - if buddy: - activity = self._get_chat_activity(buddy) - if activity: - activity.execute('message', [message]) diff --git a/shell/Makefile.am b/shell/Makefile.am index 4a1d49b..a4fd719 100644 --- a/shell/Makefile.am +++ b/shell/Makefile.am @@ -1,24 +1,14 @@ -SUBDIRS = conf data frame home PresenceService +SUBDIRS = conf data model view bin_SCRIPTS = \ sugar \ sugar-activity \ sugar-activity-factory \ - sugar-console \ - sugar-presence-service + sugar-console sugardir = $(pkgdatadir)/shell sugar_PYTHON = \ __init__.py \ - ActivityHost.py \ - ChatController.py \ - ConsoleWindow.py \ - FirstTimeDialog.py \ - FriendPopup.py \ - Friends.py \ - Invites.py \ - Owner.py \ - Shell.py \ Session.py EXTRA_DIST = $(bin_SCRIPTS) diff --git a/shell/PresenceService/Activity.py b/shell/PresenceService/Activity.py deleted file mode 100644 index 6dc9497..0000000 --- a/shell/PresenceService/Activity.py +++ /dev/null @@ -1,171 +0,0 @@ -import dbus - -PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp" -ACTIVITY_DBUS_OBJECT_PATH = "/org/laptop/Presence/Activities/" -ACTIVITY_DBUS_INTERFACE = "org.laptop.Presence.Activity" - -class NotFoundError(Exception): - pass - -class ActivityDBusHelper(dbus.service.Object): - def __init__(self, parent, bus_name, object_path): - self._parent = parent - self._bus_name = bus_name - self._object_path = object_path - dbus.service.Object.__init__(self, bus_name, self._object_path) - - @dbus.service.method(ACTIVITY_DBUS_INTERFACE, - in_signature="s", out_signature="ao") - def getServicesOfType(self, stype): - services = self._parent.get_services_of_type(stype) - if not services: - raise NotFoundError("Not found") - ret = [] - for serv in services: - ret.append(serv.object_path()) - return ret - - @dbus.service.method(ACTIVITY_DBUS_INTERFACE, - in_signature="", out_signature="ao") - def getServices(self): - services = self._parent.get_services() - if not services: - raise NotFoundError("Not found") - ret = [] - for serv in services: - ret.append(serv.object_path()) - return ret - - @dbus.service.method(ACTIVITY_DBUS_INTERFACE, - in_signature="", out_signature="s") - def getId(self): - return self._parent.get_id() - - @dbus.service.method(ACTIVITY_DBUS_INTERFACE, - in_signature="", out_signature="s") - def getColor(self): - return self._parent.get_color() - - @dbus.service.method(ACTIVITY_DBUS_INTERFACE, - in_signature="", out_signature="ao") - def getJoinedBuddies(self): - buddies = self._parent.get_joined_buddies() - if not buddies: - raise NotFoundError("Not found") - ret = [] - for buddy in buddies: - ret.append(buddy.object_path()) - return ret - - @dbus.service.signal(ACTIVITY_DBUS_INTERFACE, - signature="o") - def ServiceAppeared(self, object_path): - pass - - @dbus.service.signal(ACTIVITY_DBUS_INTERFACE, - signature="o") - def ServiceDisappeared(self, object_path): - pass - - @dbus.service.signal(ACTIVITY_DBUS_INTERFACE, - signature="o") - def BuddyJoined(self, object_path): - pass - - @dbus.service.signal(ACTIVITY_DBUS_INTERFACE, - signature="o") - def BuddyLeft(self, object_path): - pass - - -class Activity(object): - def __init__(self, bus_name, object_id, initial_service): - if not initial_service.get_activity_id(): - raise ValueError("Service must have a valid Activity ID") - self._activity_id = initial_service.get_activity_id() - - self._buddies = [] - self._services = {} # service type -> list of Services - self._color = None - self._valid = False - - self._object_id = object_id - self._object_path = "/org/laptop/Presence/Activities/%d" % self._object_id - self._dbus_helper = ActivityDBusHelper(self, bus_name, self._object_path) - - self.add_service(initial_service) - - def object_path(self): - return dbus.ObjectPath(self._object_path) - - def is_valid(self): - """An activity is only valid when it's color is available.""" - return self._valid - - def get_id(self): - return self._activity_id - - def get_color(self): - return self._color - - def get_services(self): - ret = [] - for serv_list in self._services.values(): - for service in serv_list: - if service not in ret: - ret.append(service) - return ret - - def get_services_of_type(self, stype): - if self._services.has_key(stype): - return self._services[stype] - return None - - def get_joined_buddies(self): - buddies = [] - for serv_list in self._services.values(): - for serv in serv_list: - owner = serv.get_owner() - if not owner in buddies and owner.is_valid(): - buddies.append(owner) - return buddies - - def add_service(self, service): - stype = service.get_type() - if not self._services.has_key(stype): - self._services[stype] = [] - - if not self._color: - color = service.get_one_property('color') - if color: - self._color = color - self._valid = True - - # Send out the BuddyJoined signal if this is the first - # service from the buddy that we've seen - buddies = self.get_joined_buddies() - serv_owner = service.get_owner() - if serv_owner and serv_owner not in buddies and serv_owner.is_valid(): - self._dbus_helper.BuddyJoined(serv_owner.object_path()) - serv_owner.add_activity(self) - - if not service in self._services[stype]: - self._services[stype].append(service) - self._dbus_helper.ServiceAppeared(service.object_path()) - - def remove_service(self, service): - stype = service.get_type() - if not self._services.has_key(stype): - return - self._services[stype].remove(service) - self._dbus_helper.ServiceDisappeared(service.object_path()) - if len(self._services[stype]) == 0: - del self._services[stype] - - # Send out the BuddyLeft signal if this is the last - # service from the buddy - buddies = self.get_joined_buddies() - serv_owner = service.get_owner() - if serv_owner and serv_owner not in buddies and serv_owner.is_valid(): - serv_owner.remove_activity(self) - self._dbus_helper.BuddyLeft(serv_owner.object_path()) diff --git a/shell/PresenceService/Buddy.py b/shell/PresenceService/Buddy.py deleted file mode 100644 index 5d13c29..0000000 --- a/shell/PresenceService/Buddy.py +++ /dev/null @@ -1,402 +0,0 @@ -import base64 -import logging - -import gobject -import dbus, dbus.service -from sugar import env - - -PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp" -BUDDY_DBUS_OBJECT_PATH = "/org/laptop/Presence/Buddies/" -BUDDY_DBUS_INTERFACE = "org.laptop.Presence.Buddy" - -class NotFoundError(Exception): - pass - -class BuddyDBusHelper(dbus.service.Object): - def __init__(self, parent, bus_name, object_path): - self._parent = parent - self._bus_name = bus_name - self._object_path = object_path - dbus.service.Object.__init__(self, bus_name, self._object_path) - - @dbus.service.signal(BUDDY_DBUS_INTERFACE, - signature="o") - def ServiceAppeared(self, object_path): - pass - - @dbus.service.signal(BUDDY_DBUS_INTERFACE, - signature="o") - def ServiceDisappeared(self, object_path): - pass - - @dbus.service.signal(BUDDY_DBUS_INTERFACE, - signature="") - def IconChanged(self): - pass - - @dbus.service.signal(BUDDY_DBUS_INTERFACE, - signature="o") - def JoinedActivity(self, object_path): - pass - - @dbus.service.signal(BUDDY_DBUS_INTERFACE, - signature="o") - def LeftActivity(self, object_path): - pass - - @dbus.service.signal(BUDDY_DBUS_INTERFACE, - signature="as") - def PropertyChanged(self, prop_list): - pass - - @dbus.service.method(BUDDY_DBUS_INTERFACE, - in_signature="", out_signature="ay") - def getIcon(self): - icon = self._parent.get_icon() - if not icon: - return "" - return icon - - @dbus.service.method(BUDDY_DBUS_INTERFACE, - in_signature="s", out_signature="o") - def getServiceOfType(self, stype): - service = self._parent.get_service_of_type(stype) - if not service: - raise NotFoundError("Not found") - return service.object_path() - - @dbus.service.method(BUDDY_DBUS_INTERFACE, - in_signature="", out_signature="ao") - def getJoinedActivities(self): - acts = [] - for act in self._parent.get_joined_activities(): - acts.append(act.object_path()) - return acts - - @dbus.service.method(BUDDY_DBUS_INTERFACE, - in_signature="", out_signature="a{sv}") - def getProperties(self): - props = {} - props['name'] = self._parent.get_name() - addr = self._parent.get_address() - if addr: - props['ip4_address'] = addr - props['owner'] = self._parent.is_owner() - color = self._parent.get_color() - if color: - props['color'] = self._parent.get_color() - return props - - -class Buddy(object): - """Represents another person on the network and keeps track of the - activities and resources they make available for sharing.""" - - def __init__(self, bus_name, object_id, service): - if not bus_name: - raise ValueError("DBus bus name must be valid") - if not object_id or type(object_id) != type(1): - raise ValueError("object id must be a valid number") - # Normal Buddy objects must be created with a valid service, - # owner objects do not - if not isinstance(self, Owner): - if not isinstance(service, Service.Service): - raise ValueError("service must be a valid service object") - - self._services = {} - self._activities = {} - - self._nick_name = None - self._address = None - if service is not None: - self._nick_name = service.get_name() - self._address = service.get_source_address() - self._color = None - self._valid = False - self._icon = None - self._icon_tries = 0 - - self._object_id = object_id - self._object_path = BUDDY_DBUS_OBJECT_PATH + str(self._object_id) - self._dbus_helper = BuddyDBusHelper(self, bus_name, self._object_path) - - if service is not None: - self.add_service(service) - - def object_path(self): - return dbus.ObjectPath(self._object_path) - - def _request_buddy_icon_cb(self, result_status, response, user_data): - """Callback when icon request has completed.""" - from sugar.p2p import network - icon = response - service = user_data - if result_status == network.RESULT_SUCCESS: - if icon and len(icon): - icon = base64.b64decode(icon) - logging.debug("Buddy icon for '%s' is size %d" % (self._nick_name, len(icon))) - self._set_icon(icon) - - if (result_status == network.RESULT_FAILED or not icon) and self._icon_tries < 3: - self._icon_tries = self._icon_tries + 1 - logging.debug("Failed to retrieve buddy icon for '%s' on try %d of %d" % (self._nick_name, \ - self._icon_tries, 3)) - gobject.timeout_add(1000, self._request_buddy_icon, service) - return False - - def _request_buddy_icon(self, service): - """Contact the buddy to retrieve the buddy icon.""" - from sugar.p2p import Stream - buddy_stream = Stream.Stream.new_from_service(service, start_reader=False) - writer = buddy_stream.new_writer(service) - success = writer.custom_request("get_buddy_icon", self._request_buddy_icon_cb, service) - if not success: - del writer, buddy_stream - gobject.timeout_add(1000, self._request_buddy_icon, service) - return False - - def _get_service_key(self, service): - return (service.get_type(), service.get_activity_id()) - - def add_service(self, service): - """Adds a new service to this buddy's service list, returning - True if the service was successfully added, and False if it was not.""" - if service.get_name() != self._nick_name: - logging.error("Service and buddy nick names doesn't match: " \ - "%s %s" % (service.get_name(), self._nick_name)) - return False - - source_addr = service.get_source_address() - if source_addr != self._address: - logging.error("Service source and buddy address doesn't " \ - "match: %s %s" % (source_addr, self._address)) - return False - return self._internal_add_service(service) - - def _internal_add_service(self, service): - service_key = self._get_service_key(service) - if service_key in self._services.keys(): - logging.error("Service already known: %s %s" % (service_key[0], - service_key[1])) - return False - - logging.debug("Buddy %s added service type %s id %s" % (self._nick_name, - service.get_type(), service.get_activity_id())) - self._services[service_key] = service - service.set_owner(self) - - if service.get_type() == PRESENCE_SERVICE_TYPE: - # A buddy isn't valid until its official presence - # service has been found and resolved - self._valid = True - print 'Requesting buddy icon %s' % self._nick_name - self._request_buddy_icon(service) - self._color = service.get_one_property('color') - if self._color: - self._dbus_helper.PropertyChanged(['color']) - - if self._valid: - self._dbus_helper.ServiceAppeared(service.object_path()) - return True - - def add_activity(self, activity): - actid = activity.get_id() - if activity in self._activities.values(): - raise RuntimeError("Tried to add activity twice") - found = False - for serv in self._services.values(): - if serv.get_activity_id() == activity.get_id(): - found = True - break - if not found: - raise RuntimeError("Tried to add activity for which we had no service") - self._activities[actid] = activity - if activity.is_valid(): - self._dbus_helper.JoinedActivity(activity.object_path()) - - def remove_service(self, service): - """Remove a service from a buddy; ie, the activity was closed - or the buddy went away.""" - if service.get_source_address() != self._address: - return - if service.get_name() != self._nick_name: - return - service_key = self._get_service_key(service) - if self._services.has_key(service_key): - if self._valid: - self._dbus_helper.ServiceDisappeared(service.object_path()) - del self._services[service_key] - - if service.get_type() == PRESENCE_SERVICE_TYPE: - self._valid = False - - def remove_activity(self, activity): - actid = activity.get_id() - if not self._activities.has_key(actid): - return - del self._activities[actid] - if activity.is_valid(): - self._dbus_helper.LeftActivity(activity.object_path()) - - def get_joined_activities(self): - acts = [] - for act in self._activities.values(): - if act.is_valid(): - acts.append(act) - return acts - - def get_service_of_type(self, stype=None, activity=None): - """Return a service of a certain type, or None if the buddy - doesn't provide that service.""" - if not stype: - raise RuntimeError("Need to specify a service type.") - - if activity and not activity.is_valid(): - raise RuntimeError("Activity is not yet valid.") - - if activity: - key = (stype, activity.get_id()) - else: - key = (stype, None) - if self._services.has_key(key): - return self._services[key] - return None - - def is_valid(self): - """Return whether the buddy is valid or not. A buddy is - not valid until its official presence service has been found - and successfully resolved.""" - return self._valid - - def get_icon(self): - """Return the buddies icon, if any.""" - return self._icon - - def get_address(self): - return self._address - - def get_name(self): - return self._nick_name - - def get_color(self): - return self._color - - def _set_icon(self, icon): - """Can only set icon for other buddies. The Owner - takes care of setting it's own icon.""" - if icon != self._icon: - self._icon = icon - self._dbus_helper.IconChanged() - - def is_owner(self): - return False - - -class Owner(Buddy): - """Class representing the owner of the machine. This is the client - portion of the Owner, paired with the server portion in Owner.py.""" - def __init__(self, ps, bus_name, object_id): - Buddy.__init__(self, bus_name, object_id, None) - self._nick_name = env.get_nick_name() - self._color = env.get_color() - self._ps = ps - - def add_service(self, service): - """Adds a new service to this buddy's service list, returning - True if the service was successfully added, and False if it was not.""" - if service.get_name() != self._nick_name: - logging.error("Service and buddy nick names doesn't match: " \ - "%s %s" % (service.get_name(), self._nick_name)) - return False - - # The Owner initially doesn't have an address, so the first - # service added to the Owner determines the owner's address - source_addr = service.get_source_address() - if self._address is None and service.is_local(): - self._address = source_addr - self._dbus_helper.PropertyChanged(['ip4_address']) - - # The owner bypasses address checks and only cares if - # avahi says the service is a local service - if not service.is_local(): - logging.error("Cannot add remote service to owner object.") - return False - - logging.debug("Adding owner service %s.%s at %s:%d." % (service.get_name(), - service.get_type(), service.get_source_address(), - service.get_port())) - return self._internal_add_service(service) - - def is_owner(self): - return True - - -################################################################# -# Tests -################################################################# - -import unittest -import Service - -__objid_seq = 0 -def _next_objid(): - global __objid_seq - __objid_seq = __objid_seq + 1 - return __objid_seq - - -class BuddyTestCase(unittest.TestCase): - _DEF_NAME = u"Tommy" - _DEF_STYPE = unicode(PRESENCE_SERVICE_TYPE) - _DEF_DOMAIN = u"local" - _DEF_ADDRESS = u"1.1.1.1" - _DEF_PORT = 1234 - - def __init__(self, name): - self._bus = dbus.SessionBus() - self._bus_name = dbus.service.BusName('org.laptop.Presence', bus=self._bus) - unittest.TestCase.__init__(self, name) - - def __del__(self): - del self._bus_name - del self._bus - - def _test_init_fail(self, service, fail_msg): - """Test something we expect to fail.""" - try: - objid = _next_objid() - buddy = Buddy(self._bus_name, objid, service, owner=False) - except ValueError, exc: - pass - else: - self.fail("expected a ValueError for %s." % fail_msg) - - def testService(self): - service = None - self._test_init_fail(service, "invalid service") - - def testGoodInit(self): - objid = _next_objid() - service = Service.Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, - self._DEF_ADDRESS, self._DEF_PORT) - objid = _next_objid() - buddy = Buddy(self._bus_name, objid, service) - assert buddy.get_name() == self._DEF_NAME, "buddy name wasn't correct after init." - assert buddy.get_address() == self._DEF_ADDRESS, "buddy address wasn't correct after init." - assert buddy.object_path() == BUDDY_DBUS_OBJECT_PATH + str(objid) - - def addToSuite(suite): - suite.addTest(BuddyTestCase("testService")) - suite.addTest(BuddyTestCase("testGoodInit")) - addToSuite = staticmethod(addToSuite) - - -def main(): - suite = unittest.TestSuite() - BuddyTestCase.addToSuite(suite) - runner = unittest.TextTestRunner() - runner.run(suite) - -if __name__ == "__main__": - main() diff --git a/shell/PresenceService/Makefile.am b/shell/PresenceService/Makefile.am deleted file mode 100644 index 1710d54..0000000 --- a/shell/PresenceService/Makefile.am +++ /dev/null @@ -1,18 +0,0 @@ -servicedir = $(datadir)/sugar/services -service_in_files = org.laptop.Presence.service.in -service_DATA = $(service_in_files:.service.in=.service) - -$(service_DATA): $(service_in_files) Makefile - @sed -e "s|\@bindir\@|$(bindir)|" $< > $@ - -sugardir = $(pkgdatadir)/shell/PresenceService -sugar_PYTHON = \ - __init__.py \ - Activity.py \ - Buddy.py \ - PresenceService.py \ - Service.py - -DISTCLEANFILES = $(service_DATA) - -EXTRA_DIST = $(service_in_files) diff --git a/shell/PresenceService/PresenceService.py b/shell/PresenceService/PresenceService.py deleted file mode 100644 index c04084c..0000000 --- a/shell/PresenceService/PresenceService.py +++ /dev/null @@ -1,785 +0,0 @@ -import avahi, dbus, dbus.glib, gobject -import Buddy -import Service -import Activity -import random -import logging -from sugar import util - - -_SA_UNRESOLVED = 0 -_SA_RESOLVE_PENDING = 1 -_SA_RESOLVED = 2 -class ServiceAdv(object): - """Wrapper class to track services from Avahi.""" - def __init__(self, interface, protocol, name, stype, domain, local): - self._interface = interface - self._protocol = protocol - if type(name) != type(u""): - raise ValueError("service advertisement name must be unicode.") - self._name = name - if type(stype) != type(u""): - raise ValueError("service advertisement type must be unicode.") - self._stype = stype - if type(domain) != type(u""): - raise ValueError("service advertisement domain must be unicode.") - self._domain = domain - self._service = None - if type(local) != type(False): - raise ValueError("local must be a boolean.") - self._local = local - self._state = _SA_UNRESOLVED - - def interface(self): - return self._interface - def protocol(self): - return self._protocol - def name(self): - return self._name - def stype(self): - return self._stype - def domain(self): - return self._domain - def is_local(self): - return self._local - def service(self): - return self._service - def set_service(self, service): - if not isinstance(service, Service.Service): - raise ValueError("must be a valid service.") - self._service = service - def state(self): - return self._state - def set_state(self, state): - if state == _SA_RESOLVE_PENDING: - if self._state == _SA_RESOLVED: - raise ValueError("Can't reset to resolve pending from resolved.") - self._state = state - -class RegisteredServiceType(object): - def __init__(self, stype): - self._stype = stype - self._refcount = 1 - - def get_type(self): - return self._stype - - def ref(self): - self._refcount += 1 - - def unref(self): - self._refcount -= 1 - return self._refcount - -def _txt_to_dict(txt): - """Convert an avahi-returned TXT record formatted - as nested arrays of integers (from dbus) into a dict - of key/value string pairs.""" - prop_dict = {} - props = avahi.txt_array_to_string_array(txt) - for item in props: - key = value = None - if '=' not in item: - # No = means a boolean value of true - key = item - value = True - else: - (key, value) = item.split('=') - prop_dict[key] = value - return prop_dict - - -_PRESENCE_SERVICE = "org.laptop.Presence" -_PRESENCE_DBUS_INTERFACE = "org.laptop.Presence" -_PRESENCE_OBJECT_PATH = "/org/laptop/Presence" - -class NotFoundError(Exception): - pass - -class PresenceServiceDBusHelper(dbus.service.Object): - def __init__(self, parent, bus_name): - self._parent = parent - self._bus_name = bus_name - dbus.service.Object.__init__(self, bus_name, _PRESENCE_OBJECT_PATH) - - @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, - signature="o") - def BuddyAppeared(self, object_path): - pass - - @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, - signature="o") - def BuddyDisappeared(self, object_path): - pass - - @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, - signature="o") - def ServiceAppeared(self, object_path): - pass - - @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, - signature="o") - def ServiceDisappeared(self, object_path): - pass - - @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, - signature="o") - def ActivityAppeared(self, object_path): - pass - - @dbus.service.signal(_PRESENCE_DBUS_INTERFACE, - signature="o") - def ActivityDisappeared(self, object_path): - pass - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="", out_signature="ao") - def getServices(self): - services = self._parent.get_services() - ret = [] - for serv in services: - ret.append(serv.object_path()) - return ret - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="s", out_signature="ao") - def getServicesOfType(self, stype): - services = self._parent.get_services_of_type(stype) - ret = [] - for serv in services: - ret.append(serv.object_path()) - return ret - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="", out_signature="ao") - def getActivities(self): - activities = self._parent.get_activities() - ret = [] - for act in activities: - ret.append(act.object_path()) - return ret - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="s", out_signature="o") - def getActivity(self, actid): - act = self._parent.get_activity(actid) - if not act: - raise NotFoundError("Not found") - return act.object_path() - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="", out_signature="ao") - def getBuddies(self): - buddies = self._parent.get_buddies() - ret = [] - for buddy in buddies: - ret.append(buddy.object_path()) - return ret - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="s", out_signature="o") - def getBuddyByName(self, name): - buddy = self._parent.get_buddy_by_name(name) - if not buddy: - raise NotFoundError("Not found") - return buddy.object_path() - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="s", out_signature="o") - def getBuddyByAddress(self, addr): - buddy = self._parent.get_buddy_by_address(addr) - if not buddy: - raise NotFoundError("Not found") - return buddy.object_path() - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="", out_signature="o") - def getOwner(self): - owner = self._parent.get_owner() - if not owner: - raise NotFoundError("Not found") - return owner.object_path() - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="os", out_signature="o", - sender_keyword="sender") - def joinActivity(self, activity_op, stype, sender): - found_activity = None - acts = self._parent.get_activities() - for act in acts: - if act.object_path() == activity_op: - found_activity = act - break - if not found_activity: - raise NotFoundError("The activity %s was not found." % activity_op) - return self._parent.join_activity(found_activity, stype, sender) - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="ssa{ss}sis", out_signature="o", - sender_keyword="sender") - def shareActivity(self, activity_id, stype, properties, address, port, - domain, sender=None): - if not len(address): - address = None - service = self._parent.share_activity(activity_id, stype, properties, address, - port, domain, sender) - return service.object_path() - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="ssa{ss}sis", out_signature="o", - sender_keyword="sender") - def registerService(self, name, stype, properties, address, port, domain, - sender=None): - if not len(address): - address = None - service = self._parent.register_service(name, stype, properties, address, - port, domain, sender) - return service.object_path() - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="o", out_signature="", - sender_keyword="sender") - def unregisterService(self, service_op, sender): - found_serv = None - services = self._parent.get_services() - for serv in services: - if serv.object_path() == service_op: - found_serv = serv - break - if not found_serv: - raise NotFoundError("The activity %s was not found." % service_op) - return self._parent.unregister_service(found_serv, sender) - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="s", out_signature="") - def registerServiceType(self, stype): - self._parent.register_service_type(stype) - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE, - in_signature="s", out_signature="") - def unregisterServiceType(self, stype): - self._parent.unregister_service_type(stype) - - @dbus.service.method(_PRESENCE_DBUS_INTERFACE) - def start(self): - self._parent.start() - -class PresenceService(object): - def __init__(self): - # interface -> IP address: interfaces we've gotten events on so far - self._local_addrs = {} - - self._next_object_id = 0 - - self._buddies = {} # nick -> Buddy - self._services = {} # (name, type) -> Service - self._activities = {} # activity id -> Activity - - # Keep track of stuff we're already browsing - self._service_type_browsers = {} - self._service_browsers = {} - - # Resolved service list - self._service_advs = [] - - # Service types we care about resolving - self._registered_service_types = [] - - # Set up the dbus service we provide - self._session_bus = dbus.SessionBus() - self._bus_name = dbus.service.BusName(_PRESENCE_SERVICE, bus=self._session_bus) - self._dbus_helper = PresenceServiceDBusHelper(self, self._bus_name) - - # Our owner object - objid = self._get_next_object_id() - self._owner = Buddy.Owner(self, self._bus_name, objid) - self._buddies[self._owner.get_name()] = self._owner - - self._started = False - - def start(self): - if self._started: - return - self._started = True - - # Connect to Avahi for mDNS stuff - self._system_bus = dbus.SystemBus() - self._mdns_service = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, - avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) - - # Always browse .local - self._new_domain_cb(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, "local") - - # Connect to Avahi and start looking for stuff - domain_browser = self._mdns_service.DomainBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, - "", avahi.DOMAIN_BROWSER_BROWSE, dbus.UInt32(0)) - db = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, domain_browser), avahi.DBUS_INTERFACE_DOMAIN_BROWSER) - db.connect_to_signal('ItemNew', self._new_domain_cb_glue) - - def _get_next_object_id(self): - """Increment and return the object ID counter.""" - self._next_object_id = self._next_object_id + 1 - return self._next_object_id - - def get_services(self): - return self._services.values() - - def get_services_of_type(self, stype): - ret = [] - for serv in self._services.values(): - if serv.get_type() == stype: - ret.append(serv) - return ret - - def get_activities(self): - # Only return valid activities - ret = [] - for act in self._activities.values(): - if act.is_valid(): - ret.append(act) - return ret - - def get_activity(self, actid): - if self._activities.has_key(actid): - act = self._activities[actid] - if act.is_valid(): - return act - return None - - def get_buddies(self): - buddies = [] - for buddy in self._buddies.values(): - if buddy.is_valid(): - buddies.append(buddy) - return buddies - - def get_buddy_by_name(self, name): - if self._buddies.has_key(name): - if self._buddies[name].is_valid(): - return self._buddies[name] - return None - - def get_buddy_by_address(self, address): - for buddy in self._buddies.values(): - if buddy.get_address() == address and buddy.is_valid(): - return buddy - return None - - def get_owner(self): - return self._owner - - def _find_service_adv(self, interface=None, protocol=None, name=None, - stype=None, domain=None, local=None): - """Search a list of service advertisements for ones matching - certain criteria.""" - adv_list = [] - for adv in self._service_advs: - if interface and adv.interface() != interface: - continue - if protocol and adv.protocol() != protocol: - continue - if name and adv.name() != name: - continue - if stype and adv.stype() != stype: - continue - if domain and adv.domain() != domain: - continue - if local is not None and adv.is_local() != local: - continue - adv_list.append(adv) - return adv_list - - def _handle_new_service_for_buddy(self, service, local): - """Deal with a new discovered service object.""" - # Once a service is resolved, we match it up to an existing buddy, - # or create a new Buddy if this is the first service known about the buddy - buddy_was_valid = False - name = service.get_name() - buddy = None - try: - buddy = self._buddies[name] - buddy_was_valid = buddy.is_valid() - service_added = buddy.add_service(service) - if service_added: - self._dbus_helper.ServiceAppeared(service.object_path()) - except KeyError: - source_addr = service.get_source_address() - objid = self._get_next_object_id() - buddy = Buddy.Buddy(self._bus_name, objid, service) - self._buddies[name] = buddy - self._dbus_helper.ServiceAppeared(service.object_path()) - if not buddy_was_valid and buddy.is_valid(): - self._dbus_helper.BuddyAppeared(buddy.object_path()) - return buddy - - def _handle_new_activity_service(self, service): - # If the serivce is an activity service, merge it into our activities list - actid = service.get_activity_id() - if not actid: - return - activity = None - was_valid = False - if not self._activities.has_key(actid): - objid = self._get_next_object_id() - activity = Activity.Activity(self._bus_name, objid, service) - self._activities[actid] = activity - else: - activity = self._activities[actid] - was_valid = activity.is_valid() - - if activity: - activity.add_service(service) - - if not was_valid and activity.is_valid(): - self._dbus_helper.ActivityAppeared(activity.object_path()) - - def _handle_remove_activity_service(self, service): - actid = service.get_activity_id() - if not actid: - return - if not self._activities.has_key(actid): - return - activity = self._activities[actid] - activity.remove_service(service) - if len(activity.get_services()) == 0: - # Kill the activity - self._dbus_helper.ActivityDisappeared(activity.object_path()) - del self._activities[actid] - - def _resolve_service_reply_cb(self, adv, interface, protocol, full_name, - stype, domain, host, aprotocol, address, port, txt, flags): - """When the service discovery finally gets here, we've got enough - information about the service to assign it to a buddy.""" - logging.debug("Resolved service '%s' type '%s' domain '%s' to " \ - " %s:%s" % (full_name, stype, domain, address, port)) - - if not adv in self._service_advs: - return False - if adv.state() != _SA_RESOLVED: - return False - - # See if we know about this service already - key = (full_name, stype) - if not self._services.has_key(key): - objid = self._get_next_object_id() - props = _txt_to_dict(txt) - service = Service.Service(self._bus_name, objid, name=full_name, - stype=stype, domain=domain, address=address, port=port, - properties=props, source_address=address, local=adv.is_local()) - self._services[key] = service - else: - # Already tracking this service; likely we were the one that shared it - # in the first place, and therefore the source address would not have - # been set yet - service = self._services[key] - if not service.get_source_address(): - service.set_source_address(address) - if not service.get_address(): - service.set_address(address) - adv.set_service(service) - - # Merge the service into our buddy and activity lists, if needed - buddy = self._handle_new_service_for_buddy(service, adv.is_local()) - if buddy and service.get_activity_id(): - self._handle_new_activity_service(service) - - return False - - def _resolve_service_reply_cb_glue(self, adv, interface, protocol, name, - stype, domain, host, aprotocol, address, port, txt, flags): - adv.set_state(_SA_RESOLVED) - gobject.idle_add(self._resolve_service_reply_cb, adv, interface, - protocol, name, stype, domain, host, aprotocol, address, - port, txt, flags) - - def _resolve_service_error_handler(self, adv, err): - adv.set_state(_SA_UNRESOLVED) - logging.error("Error resolving service %s.%s: %s" % (adv.name(), adv.stype(), err)) - - def _resolve_service(self, adv): - """Resolve and lookup a ZeroConf service to obtain its address and TXT records.""" - # Ask avahi to resolve this particular service - self._mdns_service.ResolveService(int(adv.interface()), int(adv.protocol()), adv.name(), - adv.stype(), adv.domain(), avahi.PROTO_UNSPEC, dbus.UInt32(0), - reply_handler=lambda *args: self._resolve_service_reply_cb_glue(adv, *args), - error_handler=lambda *args: self._resolve_service_error_handler(adv, *args)) - return False - - def _service_appeared_cb(self, interface, protocol, full_name, stype, domain, flags): - local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0 - adv_list = self._find_service_adv(interface=interface, protocol=protocol, - name=full_name, stype=stype, domain=domain, local=local) - adv = None - if not adv_list: - adv = ServiceAdv(interface=interface, protocol=protocol, name=full_name, - stype=stype, domain=domain, local=local) - self._service_advs.append(adv) - else: - adv = adv_list[0] - - # Decompose service name if we can - (actid, buddy_name) = Service.decompose_service_name(full_name) - - # If we care about the service right now, resolve it - resolve = False - if actid is not None or stype in self._registered_service_types: - resolve = True - if resolve and adv.state() == _SA_UNRESOLVED: - logging.debug("Found '%s' (%d) of type '%s' in domain" \ - " '%s' on %i.%i; will resolve." % (full_name, flags, stype, - domain, interface, protocol)) - adv.set_state(_SA_RESOLVE_PENDING) - gobject.idle_add(self._resolve_service, adv) - - return False - - def _service_appeared_cb_glue(self, interface, protocol, name, stype, domain, flags): - gobject.idle_add(self._service_appeared_cb, interface, protocol, name, stype, domain, flags) - - def _service_disappeared_cb(self, interface, protocol, full_name, stype, domain, flags): - local = flags & avahi.LOOKUP_RESULT_OUR_OWN > 0 - # If it's an unresolved service, remove it from our unresolved list - adv_list = self._find_service_adv(interface=interface, protocol=protocol, - name=full_name, stype=stype, domain=domain, local=local) - if not adv_list: - return False - - # Get the service object; if none, we have nothing left to do - adv = adv_list[0] - service = adv.service() - self._service_advs.remove(adv) - del adv - if not service: - return False - - logging.debug("Service %s.%s in domain %s on %i.%i disappeared." % (full_name, - stype, domain, interface, protocol)) - - self._dbus_helper.ServiceDisappeared(service.object_path()) - self._handle_remove_activity_service(service) - - # Decompose service name if we can - (actid, buddy_name) = Service.decompose_service_name(full_name) - - # Remove the service from the buddy - try: - buddy = self._buddies[buddy_name] - except KeyError: - pass - else: - buddy.remove_service(service) - if not buddy.is_valid(): - self._dbus_helper.BuddyDisappeared(buddy.object_path()) - del self._buddies[buddy_name] - key = (service.get_full_name(), service.get_type()) - del self._services[key] - return False - - def _service_disappeared_cb_glue(self, interface, protocol, name, stype, domain, flags): - gobject.idle_add(self._service_disappeared_cb, interface, protocol, name, stype, domain, flags) - - def _new_service_type_cb(self, interface, protocol, stype, domain, flags): - # Are we already browsing this domain for this type? - if self._service_browsers.has_key((interface, protocol, stype, domain)): - return - - # Start browsing for all services of this type in this domain - s_browser = self._mdns_service.ServiceBrowserNew(interface, protocol, stype, domain, dbus.UInt32(0)) - browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, s_browser), - avahi.DBUS_INTERFACE_SERVICE_BROWSER) - browser_obj.connect_to_signal('ItemNew', self._service_appeared_cb_glue) - browser_obj.connect_to_signal('ItemRemove', self._service_disappeared_cb_glue) - - self._service_browsers[(interface, protocol, stype, domain)] = browser_obj - return False - - def _new_service_type_cb_glue(self, interface, protocol, stype, domain, flags): - gobject.idle_add(self._new_service_type_cb, interface, protocol, stype, domain, flags) - - def _new_domain_cb(self, interface, protocol, domain, flags=0): - """Callback from Avahi when a new domain has been found. Start - browsing the new domain.""" - # Only use .local for now... - if domain != "local": - return - - # Are we already browsing this domain? - if self._service_type_browsers.has_key((interface, protocol, domain)): - return - - # Start browsing this domain for the services its members offer - try: - st_browser = self._mdns_service.ServiceTypeBrowserNew(interface, protocol, domain, dbus.UInt32(0)) - browser_obj = dbus.Interface(self._system_bus.get_object(avahi.DBUS_NAME, st_browser), - avahi.DBUS_INTERFACE_SERVICE_TYPE_BROWSER) - except dbus.DBusException, exc: - str_exc = str(exc) - logging.error("got exception %s while attempting to browse domain %s on %i.%i" % (str_exc, domain, interface, protocol)) - if str_exc.find("The name org.freedesktop.Avahi was not provided by any .service files") >= 0: - raise Exception("Avahi does not appear to be running. '%s'" % str_exc) - else: - raise exc - logging.debug("Browsing domain '%s' on %i.%i ..." % (domain, interface, protocol)) - browser_obj.connect_to_signal('ItemNew', self._new_service_type_cb_glue) - self._service_type_browsers[(interface, protocol, domain)] = browser_obj - return False - - def _new_domain_cb_glue(self, interface, protocol, domain, flags=0): - gobject.idle_add(self._new_domain_cb, interface, protocol, domain, flags) - - def join_activity(self, activity, stype, sender): - services = activity.get_services_of_type(stype) - if not len(services): - raise NotFoundError("The service type %s was not present within " \ - "the activity %s" % (stype, activity.object_path())) - act_service = services[0] - props = act_service.get_properties() - color = activity.get_color() - if color: - props['color'] = color - return self._share_activity(activity.get_id(), stype, properties, - act_service.get_address(), act_service.get_port(), - act_service.get_domain(), sender) - - def share_activity(self, activity_id, stype, properties=None, address=None, - port=-1, domain=u"local", sender=None): - """Convenience function to share an activity with other buddies.""" - if not util.validate_activity_id(activity_id): - raise ValueError("invalid activity id") - owner_nick = self._owner.get_name() - real_name = Service.compose_service_name(owner_nick, activity_id) - if address and type(address) != type(u""): - raise ValueError("address must be a unicode string.") - if address == None and stype.endswith('_udp'): - # Use random currently unassigned multicast address - address = u"232.%d.%d.%d" % (random.randint(0, 254), random.randint(1, 254), - random.randint(1, 254)) - properties['address'] = address - properties['port'] = port - if port and port != -1 and (type(port) != type(1) or port <= 1024 or port >= 65535): - raise ValueError("port must be a number between 1024 and 65535") - - color = self._owner.get_color() - if color: - properties['color'] = color - - logging.debug('Share activity %s, type %s, address %s, port %d, " \ - "properties %s' % (activity_id, stype, address, port, - properties)) - return self.register_service(real_name, stype, properties, address, - port, domain, sender) - - def register_service(self, name, stype, properties={}, address=None, - port=-1, domain=u"local", sender=None): - """Register a new service, advertising it to other Buddies on the network.""" - (actid, person_name) = Service.decompose_service_name(name) - if self.get_owner() and person_name != self.get_owner().get_name(): - raise RuntimeError("Tried to register a service that didn't have" \ - " Owner nick as the service name!") - if not domain or not len(domain): - domain = u"local" - if not port or port == -1: - port = random.randint(4000, 65000) - - try: - obj = self._system_bus.get_object(avahi.DBUS_NAME, self._mdns_service.EntryGroupNew()) - group = dbus.Interface(obj, avahi.DBUS_INTERFACE_ENTRY_GROUP) - - # Add properties; ensure they are converted to ByteArray types - # because python sometimes can't figure that out - info = dbus.Array([], signature="aay") - for k, v in properties.items(): - info.append(dbus.types.ByteArray("%s=%s" % (k, v))) - - objid = self._get_next_object_id() - service = Service.Service(self._bus_name, objid, name=name, - stype=stype, domain=domain, address=address, port=port, - properties=properties, source_address=None, local=True, - local_publisher=sender) - self._services[(name, stype)] = service - port = service.get_port() - - logging.debug("PS: Will register service with name='%s', stype='%s'," \ - " domain='%s', address='%s', port=%d, info='%s'" % (name, stype, - domain, address, port, info)) - group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, dbus.String(name), - dbus.String(stype), dbus.String(domain), dbus.String(""), # let Avahi figure the 'host' out - dbus.UInt16(port), info) - service.set_avahi_entry_group(group) - group.Commit() - except dbus.exceptions.DBusException, exc: - # FIXME: ignore local name collisions, since that means - # the zeroconf service is already registered. Ideally we - # should un-register it an re-register with the correct info - if str(exc) == "Local name collision": - pass - self.register_service_type(stype) - return service - - def unregister_service(self, service, sender=None): - local_publisher = service.get_local_publisher() - if sender is not None and local_publisher != sender: - raise ValueError("Service was not not registered by requesting process!") - group = service.get_avahi_entry_group() - if not group: - raise ValueError("Service was not a local service provided by this laptop!") - group.Free() - - def register_service_type(self, stype): - """Requests that the Presence service look for and recognize - a certain mDNS service types.""" - if type(stype) != type(u""): - raise ValueError("service type must be a unicode string.") - - # If we've already registered it as a service type, ref it and return - for item in self._registered_service_types: - if item.get_type() == stype: - item.ref() - return - - # Otherwise track this type now - obj = RegisteredServiceType(stype) - self._registered_service_types.append(obj) - - # Find unresolved services that match the service type - # we're now interested in, and resolve them - resolv_list = [] - - # Find services of this type - resolv_list = self._find_service_adv(stype=stype) - # Request resolution for them if they aren't in-process already - for adv in resolv_list: - if adv.state() == _SA_UNRESOLVED: - adv.set_state(_SA_RESOLVE_PENDING) - gobject.idle_add(self._resolve_service, adv) - - def unregister_service_type(self, stype): - """Stop tracking a certain mDNS service.""" - if type(stype) != type(u""): - raise ValueError("service type must be a unicode string.") - item = None - for item in self._registered_service_types: - if item.get_type() == stype: - break - # if it was found, unref it and possibly remove it - if item is not None: - if item.unref() <= 0: - self._registered_service_types.remove(item) - del item - - -def main(): - from sugar import TracebackUtils - loop = gobject.MainLoop() - ps = PresenceService() - tbh = TracebackUtils.TracebackHelper() - try: - loop.run() - except KeyboardInterrupt: - print 'Ctrl+C pressed, exiting...' - - del tbh - - -if __name__ == "__main__": - main() diff --git a/shell/PresenceService/Service.py b/shell/PresenceService/Service.py deleted file mode 100644 index 0c23600..0000000 --- a/shell/PresenceService/Service.py +++ /dev/null @@ -1,488 +0,0 @@ -import avahi -import sys, os -sys.path.insert(0, os.path.abspath("../../")) -from sugar import util -import dbus, dbus.service -import random - -def compose_service_name(name, activity_id): - if type(name) == type(""): - name = unicode(name) - if not name: - raise ValueError("name must be a valid string.") - if not activity_id: - return name - if type(name) != type(u""): - raise ValueError("name must be in unicode.") - composed = "%s [%s]" % (name, activity_id) - return composed - -def decompose_service_name(name): - """Break a service name into the name and activity ID, if we can.""" - if type(name) != type(u""): - raise ValueError("name must be a valid unicode string.") - name_len = len(name) - if name_len < util.ACTIVITY_ID_LEN + 5: - return (None, name) - # check for activity id end marker - if name[name_len - 1] != "]": - return (None, name) - start = name_len - 1 - util.ACTIVITY_ID_LEN - end = name_len - 1 - # check for activity id start marker - if name[start - 1] != "[" or name[start - 2] != " ": - return (None, name) - activity_id = name[start:end] - if not util.validate_activity_id(activity_id): - return (None, name) - return (activity_id, name[:start - 2]) - - -_ACTIVITY_ID_TAG = "ActivityID" -SERVICE_DBUS_INTERFACE = "org.laptop.Presence.Service" -SERVICE_DBUS_OBJECT_PATH = "/org/laptop/Presence/Services/" - -class ServiceDBusHelper(dbus.service.Object): - """Handle dbus requests and signals for Service objects""" - def __init__(self, parent, bus_name, object_path): - self._parent = parent - self._bus_name = bus_name - self._object_path = object_path - dbus.service.Object.__init__(self, bus_name, self._object_path) - - @dbus.service.signal(SERVICE_DBUS_INTERFACE, - signature="as") - def PublishedValueChanged(self, keylist): - pass - - @dbus.service.method(SERVICE_DBUS_INTERFACE, - in_signature="", out_signature="a{sv}") - def getProperties(self): - """Return service properties.""" - pary = {} - pary['name'] = self._parent.get_name() - pary['type'] = self._parent.get_type() - pary['domain'] = self._parent.get_domain() - actid = self._parent.get_activity_id() - if actid: - pary['activityId'] = actid - port = self._parent.get_port() - if port: - pary['port'] = self._parent.get_port() - addr = self._parent.get_address() - if addr: - pary['address'] = addr - source_addr = self._parent.get_source_address() - if source_addr: - pary['sourceAddress'] = source_addr - return pary - - @dbus.service.method(SERVICE_DBUS_INTERFACE, - in_signature="s") - def getPublishedValue(self, key): - """Return the value belonging to the requested key from the - service's TXT records.""" - val = self._parent.get_one_property(key) - if not val: - raise KeyError("Value was not found.") - return val - - @dbus.service.method(SERVICE_DBUS_INTERFACE, - in_signature="", out_signature="a{sv}") - def getPublishedValues(self): - pary = {} - props = self._parent.get_properties() - for key, value in props.items(): - pary[key] = str(value) - return dbus.Dictionary(pary) - - @dbus.service.method(SERVICE_DBUS_INTERFACE, - sender_keyword="sender") - def setPublishedValue(self, key, value, sender): - self._parent.set_property(key, value, sender) - - @dbus.service.method(SERVICE_DBUS_INTERFACE, - in_signature="a{sv}", sender_keyword="sender") - def setPublishedValues(self, values, sender): - self._parent.set_properties(values, sender) - - -class Service(object): - """Encapsulates information about a specific ZeroConf/mDNS - service as advertised on the network.""" - def __init__(self, bus_name, object_id, name, stype, domain=u"local", - address=None, port=-1, properties=None, source_address=None, - local=False, local_publisher=None): - if not bus_name: - raise ValueError("DBus bus name must be valid") - if not object_id or type(object_id) != type(1): - raise ValueError("object id must be a valid number") - - # Validate immutable options - if name and type(name) != type(u""): - raise ValueError("name must be unicode.") - if not name or not len(name): - raise ValueError("must specify a valid service name.") - - if stype and type(stype) != type(u""): - raise ValueError("service type must be in unicode.") - if not stype or not len(stype): - raise ValueError("must specify a valid service type.") - if not stype.endswith("._tcp") and not stype.endswith("._udp"): - raise ValueError("must specify a TCP or UDP service type.") - - if type(domain) != type(u""): - raise ValueError("domain must be in unicode.") - if domain and domain != "local": - raise ValueError("must use the 'local' domain (for now).") - - (actid, real_name) = decompose_service_name(name) - self._name = real_name - self._full_name = name - self._stype = stype - self._domain = domain - self._port = -1 - self.set_port(port) - self._properties = {} - self._internal_set_properties(properties, first_time=True) - self._avahi_entry_group = None - self._local = local - - # Source address is the unicast source IP - self._source_address = None - if source_address is not None: - self.set_source_address(source_address) - - # Address is the published address, could be multicast or unicast - self._address = None - if self._properties.has_key('address'): - self.set_address(self._properties['address']) - elif address is not None: - self.set_address(address) - self._properties['address'] = address - - # Ensure that an ActivityID tag, if given, matches - # what we expect from the service type - if self._properties.has_key(_ACTIVITY_ID_TAG): - prop_actid = self._properties[_ACTIVITY_ID_TAG] - if (prop_actid and not actid) or (prop_actid != actid): - raise ValueError("ActivityID property specified, but the " \ - "service names's activity ID didn't match it: %s," \ - " %s" % (prop_actid, actid)) - self._activity_id = actid - if actid and not self._properties.has_key(_ACTIVITY_ID_TAG): - self._properties[_ACTIVITY_ID_TAG] = actid - - self._owner = None - - # ID of the D-Bus connection that published this service, if any. - # We only let the local publisher modify the service. - self._local_publisher = local_publisher - - # register ourselves with dbus - self._object_id = object_id - self._object_path = SERVICE_DBUS_OBJECT_PATH + str(self._object_id) - self._dbus_helper = ServiceDBusHelper(self, bus_name, self._object_path) - - def object_path(self): - return dbus.ObjectPath(self._object_path) - - def get_owner(self): - return self._owner - - def set_owner(self, owner): - if self._owner is not None: - raise RuntimeError("Can only set a service's owner once") - self._owner = owner - - def get_local_publisher(self): - return self._local_publisher - - def is_local(self): - return self._local - - def get_name(self): - """Return the service's name, usually that of the - buddy who provides it.""" - return self._name - - def get_full_name(self): - return self._full_name - - def get_one_property(self, key): - """Return one property of the service, or None - if the property was not found. Cannot distinguish - between lack of a property, and a property value that - actually is None.""" - if key in self._properties.keys(): - return self._properties[key] - return None - - def get_properties(self): - """Return a python dictionary of all the service's - properties.""" - return self._properties - - def set_property(self, key, value, sender=None): - """Set one service property""" - if sender is not None and self._local_publisher != sender: - raise ValueError("Service was not not registered by requesting process!") - - if type(key) != type(u""): - raise ValueError("Key must be a unicode string.") - if type(value) != type(u"") or type(value) != type(True): - raise ValueError("Key must be a unicode string or a boolean.") - - # Ignore setting the key to it's current value - if self._properties.has_key(key): - if self._properties[key] == value: - return - - # Blank value means remove key - remove = False - if type(value) == type(u"") and len(value) == 0: - remove = True - if type(value) == type(False) and value == False: - remove = True - - if remove: - # If the key wasn't present, return without error - if self._properties.has_key(key): - del self._properties[key] - else: - # Otherwise set it - if type(value) == type(True): - value = "" - self._properties[key] = value - self._dbus_helper.PublishedValueChanged(key) - - def set_properties(self, properties, sender=None): - """Set all service properties in one call""" - if sender is not None and self._local_publisher != sender: - raise ValueError("Service was not not registered by requesting process!") - self._internal_set_properties(properties) - - def _internal_set_properties(self, properties, first_time=False): - """Set the service's properties from either an Avahi - TXT record (a list of lists of integers), or a - python dictionary.""" - if type(properties) != type({}): - raise ValueError("Properties must be a dictionary.") - self._properties = {} - - # Set key/value pairs on internal property list - for key, value in properties.items(): - if len(key) == 0: - continue - tmp_key = key - tmp_val = value - if type(tmp_key) != type(u""): - tmp_key = unicode(tmp_key) - if type(tmp_val) != type(u""): - tmp_val = unicode(tmp_val) - self._properties[tmp_key] = tmp_val - - if not first_time: - self._dbus_helper.PublishedValueChanged(self._properties.keys()) - - def get_type(self): - """Return the service's service type.""" - return self._stype - - def get_activity_id(self): - """Return the activity ID this service is associated with, if any.""" - return self._activity_id - - def get_port(self): - return self._port - - def set_port(self, port): - if type(port) != type(1) or (port <= 1024 and port > 65536): - raise ValueError("must specify a valid port number between 1024 and 65536.") - self._port = port - - def get_source_address(self): - return self._source_address - - def set_source_address(self, address): - if not address or type(address) != type(u""): - raise ValueError("address must be unicode") - self._source_address = address - - def get_address(self): - return self._address - - def set_address(self, address): - if not address or type(address) != type(u""): - raise ValueError("address must be a unicode string") - self._address = address - self._properties['address'] = address - - def get_domain(self): - """Return the ZeroConf/mDNS domain the service was found in.""" - return self._domain - - def set_avahi_entry_group(self, group): - self._avahi_entry_group = group - - def get_avahi_entry_group(self): - return self._avahi_entry_group - -################################################################# -# Tests -################################################################# - -import unittest - -__objid_seq = 0 -def _next_objid(): - global __objid_seq - __objid_seq = __objid_seq + 1 - return __objid_seq - - -class ServiceTestCase(unittest.TestCase): - _DEF_NAME = u"foobar" - _DEF_STYPE = u"_foo._bar._tcp" - _DEF_DOMAIN = u"local" - _DEF_ADDRESS = u"1.1.1.1" - _DEF_PORT = 1234 - _DEF_PROPS = {'foobar': 'baz'} - _STR_TEST_ARGS = [None, 0, [], {}] - - def __init__(self, name): - self._bus = dbus.SessionBus() - self._bus_name = dbus.service.BusName('org.laptop.Presence', bus=self._bus) - unittest.TestCase.__init__(self, name) - - def __del__(self): - del self._bus_name - del self._bus - - def _test_init_fail(self, name, stype, domain, address, port, properties, fail_msg): - """Test something we expect to fail.""" - try: - objid = _next_objid() - service = Service(self._bus_name, objid, name, stype, domain, address, - port, properties) - except ValueError, exc: - pass - else: - self.fail("expected a ValueError for %s." % fail_msg) - - def testName(self): - for item in self._STR_TEST_ARGS: - self._test_init_fail(item, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS, - self._DEF_PORT, self._DEF_PROPS, "invalid name") - - def testType(self): - for item in self._STR_TEST_ARGS: - self._test_init_fail(self._DEF_NAME, item, self._DEF_DOMAIN, self._DEF_ADDRESS, - self._DEF_PORT, self._DEF_PROPS, "invalid service type") - self._test_init_fail(self._DEF_NAME, u"_bork._foobar", self._DEF_DOMAIN, self._DEF_ADDRESS, - self._DEF_PORT, self._DEF_PROPS, "invalid service type") - - def testDomain(self): - for item in self._STR_TEST_ARGS: - self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, item, self._DEF_ADDRESS, - self._DEF_PORT, self._DEF_PROPS, "invalid domain") - # Only accept local for now - self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, u"foobar", self._DEF_ADDRESS, - self._DEF_PORT, self._DEF_PROPS, "invalid domain") - # Make sure "" works - objid = _next_objid() - service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, u"", - self._DEF_ADDRESS, self._DEF_PORT, self._DEF_PROPS) - assert service, "Empty domain was not accepted!" - - def testAddress(self): - self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, [], - self._DEF_PORT, self._DEF_PROPS, "invalid address") - self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, {}, - self._DEF_PORT, self._DEF_PROPS, "invalid address") - self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, 1234, - self._DEF_PORT, self._DEF_PROPS, "invalid address") - - def testPort(self): - self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS, - [], self._DEF_PROPS, "invalid port") - self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS, - {}, self._DEF_PROPS, "invalid port") - self._test_init_fail(self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS, - "adf", self._DEF_PROPS, "invalid port") - - def testGoodInit(self): - objid = _next_objid() - service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, - self._DEF_ADDRESS, self._DEF_PORT, self._DEF_PROPS) - assert service.get_name() == self._DEF_NAME, "service name wasn't correct after init." - assert service.get_type() == self._DEF_STYPE, "service type wasn't correct after init." - assert service.get_domain() == "local", "service domain wasn't correct after init." - assert service.get_address() == self._DEF_ADDRESS, "service address wasn't correct after init." - assert service.get_port() == self._DEF_PORT, "service port wasn't correct after init." - assert service.object_path() == SERVICE_DBUS_OBJECT_PATH + str(objid) - value = service.get_one_property('foobar') - assert value and value == 'baz', "service property wasn't correct after init." - - def testAvahiProperties(self): - props = [[111, 114, 103, 46, 102, 114, 101, 101, 100, 101, 115, 107, 116, 111, 112, 46, 65, 118, 97, 104, 105, 46, 99, 111, 111, 107, 105, 101, 61, 50, 54, 48, 49, 53, 52, 51, 57, 53, 50]] - key = "org.freedesktop.Avahi.cookie" - expected_value = "2601543952" - objid = _next_objid() - service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, - self._DEF_ADDRESS, self._DEF_PORT, props) - value = service.get_one_property(key) - assert value and value == expected_value, "service properties weren't correct after init." - value = service.get_one_property('bork') - assert not value, "service properties weren't correct after init." - - def testBoolProperty(self): - props = [[111, 114, 103, 46, 102, 114, 101, 101, 100, 101, 115, 107, 116, 111, 112, 46, 65, 118, 97, 104, 105, 46, 99, 111, 111, 107, 105, 101]] - key = "org.freedesktop.Avahi.cookie" - expected_value = True - objid = _next_objid() - service = Service(self._bus_name, objid, self._DEF_NAME, self._DEF_STYPE, self._DEF_DOMAIN, self._DEF_ADDRESS, - self._DEF_PORT, props) - value = service.get_one_property(key) - assert value is not None and value == expected_value, "service properties weren't correct after init." - - def testActivityService(self): - # Valid group service type, non-multicast address - actid = "4569a71b80805aa96a847f7ac1c407327b3ec2b4" - name = compose_service_name("Tommy", actid) - - # Valid activity service name, None address - objid = _next_objid() - service = Service(self._bus_name, objid, name, self._DEF_STYPE, self._DEF_DOMAIN, None, - self._DEF_PORT, self._DEF_PROPS) - assert service.get_address() == None, "address was not None as expected!" - assert service.get_activity_id() == actid, "activity id was different than expected!" - - # Valid activity service name and multicast address, ensure it works - mc_addr = u"224.0.0.34" - objid = _next_objid() - service = Service(self._bus_name, objid, name, self._DEF_STYPE, self._DEF_DOMAIN, mc_addr, - self._DEF_PORT, self._DEF_PROPS) - assert service.get_address() == mc_addr, "address was not expected address!" - - def addToSuite(suite): - suite.addTest(ServiceTestCase("testName")) - suite.addTest(ServiceTestCase("testType")) - suite.addTest(ServiceTestCase("testDomain")) - suite.addTest(ServiceTestCase("testAddress")) - suite.addTest(ServiceTestCase("testPort")) - suite.addTest(ServiceTestCase("testGoodInit")) - suite.addTest(ServiceTestCase("testAvahiProperties")) - suite.addTest(ServiceTestCase("testBoolProperty")) - suite.addTest(ServiceTestCase("testActivityService")) - addToSuite = staticmethod(addToSuite) - - -def main(): - suite = unittest.TestSuite() - ServiceTestCase.addToSuite(suite) - runner = unittest.TextTestRunner() - runner.run(suite) - -if __name__ == "__main__": - main() diff --git a/shell/PresenceService/org.laptop.Presence.service.in b/shell/PresenceService/org.laptop.Presence.service.in deleted file mode 100644 index 3739d9e..0000000 --- a/shell/PresenceService/org.laptop.Presence.service.in +++ /dev/null @@ -1,4 +0,0 @@ -[D-BUS Service] -Name = org.laptop.Presence -Exec = @bindir@/sugar-presence-service - diff --git a/shell/Session.py b/shell/Session.py index 07337d6..da17cd7 100644 --- a/shell/Session.py +++ b/shell/Session.py @@ -1,8 +1,8 @@ import os import gtk -from Shell import Shell -from ConsoleWindow import ConsoleWindow +from view.Shell import Shell +from model.ShellModel import ShellModel from sugar import env from sugar import logger @@ -10,7 +10,7 @@ from sugar.session.Process import Process from sugar.session.DbusProcess import DbusProcess from sugar.session.MatchboxProcess import MatchboxProcess -from FirstTimeDialog import FirstTimeDialog +from view.FirstTimeDialog import FirstTimeDialog import conf class DBusMonitorProcess(Process): @@ -47,11 +47,8 @@ class Session: dbm = DBusMonitorProcess() dbm.start() - console = ConsoleWindow() - logger.start('Shell', console) - - shell = Shell() - shell.set_console(console) + model = ShellModel() + shell = Shell(model) from sugar import TracebackUtils tbh = TracebackUtils.TracebackHelper() diff --git a/shell/Shell.py b/shell/Shell.py deleted file mode 100755 index 277e2f9..0000000 --- a/shell/Shell.py +++ /dev/null @@ -1,174 +0,0 @@ -import os -import logging - -import dbus -import dbus.glib -import gtk -import gobject -import wnck - -from home.HomeWindow import HomeWindow -from Owner import ShellOwner -from sugar.presence import PresenceService -from ActivityHost import ActivityHost -from ChatController import ChatController -from sugar.activity import ActivityFactory -from sugar.activity import Activity -from frame.Frame import Frame -from globalkeys import KeyGrabber -import conf -import sugar - -class ShellDbusService(dbus.service.Object): - def __init__(self, shell, bus_name): - dbus.service.Object.__init__(self, bus_name, '/com/redhat/Sugar/Shell') - self._shell = shell - - def __show_console_idle(self): - self._shell.show_console() - - @dbus.service.method('com.redhat.Sugar.Shell') - def show_console(self): - gobject.idle_add(self.__show_console_idle) - -class Shell(gobject.GObject): - __gsignals__ = { - 'activity-opened': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'activity-changed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), - 'activity-closed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])) - } - - def __init__(self): - gobject.GObject.__init__(self) - - self._screen = wnck.screen_get_default() - self._hosts = {} - self._current_window = None - - self._key_grabber = KeyGrabber() - self._key_grabber.connect('key-pressed', self.__global_key_pressed_cb) - self._key_grabber.grab('F1') - self._key_grabber.grab('F2') - self._key_grabber.grab('F3') - self._key_grabber.grab('F4') - self._key_grabber.grab('F5') - self._key_grabber.grab('F6') - - self._home_window = HomeWindow(self) - self._home_window.show() - self.set_zoom_level(sugar.ZOOM_HOME) - - self._screen.connect('window-opened', self.__window_opened_cb) - self._screen.connect('window-closed', self.__window_closed_cb) - self._screen.connect('active-window-changed', - self.__active_window_changed_cb) - - session_bus = dbus.SessionBus() - bus_name = dbus.service.BusName('com.redhat.Sugar.Shell', bus=session_bus) - ShellDbusService(self, bus_name) - - PresenceService.start() - self._pservice = PresenceService.get_instance() - - self._owner = ShellOwner(self) - self._owner.announce() - - self._home_window.set_owner(self._owner) - - self._chat_controller = ChatController(self) - self._chat_controller.listen() - - self._frame = Frame(self, self._owner) - self._frame.show_and_hide(10) - - def __global_key_pressed_cb(self, grabber, key): - if key == 'F1': - self.set_zoom_level(sugar.ZOOM_ACTIVITY) - elif key == 'F2': - self.set_zoom_level(sugar.ZOOM_HOME) - elif key == 'F3': - self.set_zoom_level(sugar.ZOOM_FRIENDS) - elif key == 'F4': - self.set_zoom_level(sugar.ZOOM_MESH) - elif key == 'F5': - self._frame.toggle_visibility() - elif key == 'F6': - ActivityFactory.create('org.sugar.Terminal') - - def set_console(self, console): - self._console = console - - def __window_opened_cb(self, screen, window): - if window.get_window_type() == wnck.WINDOW_NORMAL: - host = ActivityHost(self, window) - self._hosts[window.get_xid()] = host - self.emit('activity-opened', host) - - def __active_window_changed_cb(self, screen): - window = screen.get_active_window() - if window and window.get_window_type() == wnck.WINDOW_NORMAL: - if self._current_window != window: - self._current_window = window - self.emit('activity-changed', self.get_current_activity()) - - def __window_closed_cb(self, screen, window): - if window.get_window_type() == wnck.WINDOW_NORMAL: - xid = window.get_xid() - if self._hosts.has_key(xid): - host = self._hosts[xid] - self.emit('activity-closed', host) - - del self._hosts[xid] - - def get_activity(self, activity_id): - for host in self._hosts.values(): - if host.get_id() == activity_id: - return host - return None - - def get_current_activity(self): - if self._current_window != None: - xid = self._current_window.get_xid() - return self._hosts[xid] - else: - return None - - def show_console(self): - self._console.show() - - activity = self.get_current_activity() - if activity: - registry = conf.get_activity_registry() - module = registry.get_activity(activity.get_type()) - self._console.set_page(module.get_id()) - - def join_activity(self, bundle_id, activity_id): - activity = self.get_activity(activity_id) - if activity: - activity.present() - else: - activity_ps = self._pservice.get_activity(activity_id) - - if activity_ps: - activity = ActivityFactory.create(bundle_id) - activity.join(activity_ps.object_path()) - else: - logging.error('Cannot start activity.') - - def start_activity(self, activity_type): - activity = ActivityFactory.create(activity_type) - activity.execute('test', []) - return activity - - def get_chat_controller(self): - return self._chat_controller - - def set_zoom_level(self, level): - if level == sugar.ZOOM_ACTIVITY: - self._screen.toggle_showing_desktop(False) - else: - self._screen.toggle_showing_desktop(True) - self._home_window.set_zoom_level(level) diff --git a/shell/home/FriendsGroup.py b/shell/home/FriendsGroup.py deleted file mode 100644 index 605070b..0000000 --- a/shell/home/FriendsGroup.py +++ /dev/null @@ -1,84 +0,0 @@ -import random - -import goocanvas - -from sugar.canvas.IconItem import IconItem -from home.IconLayout import IconLayout -from home.MyIcon import MyIcon -from FriendPopup import FriendPopup -from sugar.canvas.Grid import Grid - -class FriendIcon(IconItem): - def __init__(self, shell, friend): - IconItem.__init__(self, icon_name='stock-buddy', - color=friend.get_color(), size=96) - - self._shell = shell - self._friend = friend - self._popup = None - - self.connect('popup', self._popup_cb) - self.connect('popdown', self._popdown_cb) - - def get_friend(self): - return self._friend - - def _popup_cb(self, icon, x1, y1, x2, y2): - grid = Grid() - - if not self._popup: - self._popup = FriendPopup(self._shell, grid, icon.get_friend()) - - [grid_x1, grid_y1] = grid.convert_from_screen(x1, y1) - [grid_x2, grid_y2] = grid.convert_from_screen(x2, y2) - - if grid_x2 + self._popup.get_width() + 1 > Grid.ROWS: - grid_x = grid_x1 - self._popup.get_width() + 1 - else: - grid_x = grid_x2 - 1 - - grid_y = grid_y1 - - if grid_y < 0: - grid_y = 0 - if grid_y + self._popup.get_width() > Grid.ROWS: - grid_y = Grid.ROWS - self._popup.get_width() - - grid.set_constraints(self._popup, grid_x, grid_y, - self._popup.get_width(), self._popup.get_height()) - - self._popup.show() - - def _popup_destroy_cb(self, popup): - self._popup = None - - def _popdown_cb(self, friend): - if self._popup: - self._popup.connect('destroy', self._popup_destroy_cb) - self._popup.popdown() - -class FriendsGroup(goocanvas.Group): - def __init__(self, shell, friends): - goocanvas.Group.__init__(self) - - self._shell = shell - self._icon_layout = IconLayout(1200, 900) - self._friends = friends - - me = MyIcon(100) - me.translate(600 - (me.get_property('size') / 2), - 450 - (me.get_property('size') / 2)) - self.add_child(me) - - for friend in self._friends: - self.add_friend(friend) - - friends.connect('friend-added', self._friend_added_cb) - - def add_friend(self, friend): - icon = FriendIcon(self._shell, friend) - self.add_child(icon) - self._icon_layout.add_icon(icon) - - def _friend_added_cb(self, data_model, friend): - self.add_friend(friend) diff --git a/shell/model/BuddyInfo.py b/shell/model/BuddyInfo.py new file mode 100644 index 0000000..68f0a44 --- /dev/null +++ b/shell/model/BuddyInfo.py @@ -0,0 +1,24 @@ +from sugar.presence import PresenceService +from sugar.canvas.IconColor import IconColor + +class BuddyInfo: + def __init__(self, buddy=None): + if buddy: + self.set_name(buddy.get_name()) + self.set_color(buddy.get_color()) + + def set_name(self, name): + self._name = name + + def set_color(self, color_string): + self._color = IconColor(color_string) + + def get_name(self): + return self._name + + def get_color(self): + return self._color + + def get_buddy(self): + pservice = PresenceService.get_instance() + return pservice.get_buddy_by_name(self._name) diff --git a/shell/Friends.py b/shell/model/Friends.py index 6beca66..a11acc0 100644 --- a/shell/Friends.py +++ b/shell/model/Friends.py @@ -3,67 +3,54 @@ from ConfigParser import ConfigParser import gobject -from sugar.canvas.IconColor import IconColor -from sugar.presence import PresenceService +from model.BuddyInfo import BuddyInfo from sugar import env -class Friend: - def __init__(self, name, color): - self._name = name - self._color = color - - def get_name(self): - return self._name - - def get_color(self): - return IconColor(self._color) - - def get_buddy(self): - pservice = PresenceService.get_instance() - return pservice.get_buddy_by_name(self._name) - class Friends(gobject.GObject): __gsignals__ = { 'friend-added': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([object])), 'friend-removed': (gobject.SIGNAL_RUN_FIRST, - gobject.TYPE_NONE, ([object])), + gobject.TYPE_NONE, ([str])), } def __init__(self): gobject.GObject.__init__(self) - self._list = [] + self._friends = {} self._path = os.path.join(env.get_profile_path(), 'friends') self.load() def has_buddy(self, buddy): - for friend in self: - if friend.get_name() == buddy.get_name(): - return True - return False + return self._friends.has_key(buddy.get_name()) - def add_friend(self, name, color): - friend = Friend(name, color) - self._list.append(friend) + def add_friend(self, buddy_info): + self._friends[buddy_info.get_name()] = buddy_info + self.emit('friend-added', buddy_info) - self.emit('friend-added', friend) - - def add_buddy(self, buddy): + def make_friend(self, buddy): if not self.has_buddy(buddy): - self.add_friend(buddy.get_name(), buddy.get_color()) + self.add_friend(BuddyInfo(buddy)) self.save() + def remove(self, buddy_info): + del self._friends[buddy_info.get_name()] + self.save() + self.emit('friend-removed', buddy_info.get_name()) + def __iter__(self): - return self._list.__iter__() + return self._friends.values().__iter__() def load(self): cp = ConfigParser() if cp.read([self._path]): for name in cp.sections(): - self.add_friend(name, cp.get(name, 'color')) + buddy = BuddyInfo() + buddy.set_name(name) + buddy.set_color(cp.get(name, 'color')) + self.add_friend(buddy) def save(self): cp = ConfigParser() diff --git a/shell/Invites.py b/shell/model/Invites.py index 24f9913..24f9913 100644 --- a/shell/Invites.py +++ b/shell/model/Invites.py diff --git a/shell/model/Makefile.am b/shell/model/Makefile.am new file mode 100644 index 0000000..ce24668 --- /dev/null +++ b/shell/model/Makefile.am @@ -0,0 +1,8 @@ +sugardir = $(pkgdatadir)/shell/model +sugar_PYTHON = \ + __init__.py \ + BuddyInfo.py \ + Friends.py \ + Invites.py \ + Owner.py \ + ShellModel.py diff --git a/shell/Owner.py b/shell/model/Owner.py index b4781f9..24004f0 100644 --- a/shell/Owner.py +++ b/shell/model/Owner.py @@ -8,8 +8,7 @@ from sugar import env import logging from sugar.p2p import Stream from sugar.presence import PresenceService -from Friends import Friends -from Invites import Invites +from model.Invites import Invites PRESENCE_SERVICE_TYPE = "_presence_olpc._tcp" @@ -33,7 +32,7 @@ class ShellOwner(object): break self._pservice = PresenceService.get_instance() - self._friends = Friends() + self._invites = Invites() self._shell = shell @@ -42,9 +41,6 @@ class ShellOwner(object): self._pending_activity_update_timer = None self._pending_activity_update = None - def get_friends(self): - return self._friends - def get_invites(self): return self._invites diff --git a/shell/model/ShellModel.py b/shell/model/ShellModel.py new file mode 100644 index 0000000..0820b44 --- /dev/null +++ b/shell/model/ShellModel.py @@ -0,0 +1,64 @@ +import gobject + +from sugar.presence import PresenceService +from model.Friends import Friends +from model.Owner import ShellOwner + +class ShellModel(gobject.GObject): + __gsignals__ = { + 'activity-opened': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'activity-changed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])), + 'activity-closed': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([gobject.TYPE_PYOBJECT])) + } + + def __init__(self): + gobject.GObject.__init__(self) + + self._hosts = {} + self._current_activity = None + + PresenceService.start() + self._pservice = PresenceService.get_instance() + + self._owner = ShellOwner(self) + self._owner.announce() + self._friends = Friends() + + def get_friends(self): + return self._friends + + def get_invites(self): + return self._owner.get_invites() + + def get_owner(self): + return self._owner + + def add_activity(self, activity_host): + self._hosts[activity_host.get_xid()] = activity_host + self.emit('activity-opened', activity_host) + + def set_current_activity(self, activity_xid): + activity_host = self._hosts[activity_xid] + if self._current_activity == activity_host: + return + + self._current_activity = activity_host + self.emit('activity-changed', activity_host) + + def remove_activity(self, activity_xid): + if self._hosts.has_key(activity_xid): + host = self._hosts[activity_xid] + self.emit('activity-closed', host) + del self._hosts[activity_xid] + + def get_activity(self, activity_id): + for host in self._hosts.values(): + if host.get_id() == activity_id: + return host + return None + + def get_current_activity(self): + return self._current_activity diff --git a/shell/PresenceService/__init__.py b/shell/model/__init__.py index e69de29..e69de29 100644 --- a/shell/PresenceService/__init__.py +++ b/shell/model/__init__.py diff --git a/shell/sugar-presence-service b/shell/sugar-presence-service deleted file mode 100755 index fda0d12..0000000 --- a/shell/sugar-presence-service +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/python - -import logging -from PresenceService import PresenceService -import sugar.logger - -sugar.logger.start('PresenceService') - -logging.info('Starting presence service') - -PresenceService.main() diff --git a/shell/ActivityHost.py b/shell/view/ActivityHost.py index b3703e7..456406b 100644 --- a/shell/ActivityHost.py +++ b/shell/view/ActivityHost.py @@ -31,6 +31,9 @@ class ActivityHost: def get_id(self): return self._id + def get_xid(self): + return self._xid + def get_icon_name(self): return self._icon_name diff --git a/shell/view/BuddyIcon.py b/shell/view/BuddyIcon.py new file mode 100644 index 0000000..3e26b08 --- /dev/null +++ b/shell/view/BuddyIcon.py @@ -0,0 +1,108 @@ +from sugar.canvas.IconItem import IconItem +from sugar.canvas.Grid import Grid +from view.BuddyPopup import BuddyPopup + +class _PopupShell: + def __init__(self): + self._popup_controller = None + + def set_active(self, controller): + if self._popup_controller: + self._popup_controller._popdown() + self._popup_controller = controller + +class BuddyIcon(IconItem): + _popup_shell = _PopupShell() + + def __init__(self, shell, friend): + IconItem.__init__(self, icon_name='stock-buddy', + color=friend.get_color(), size=96) + + self._shell = shell + self._friend = friend + self._popup = None + self._popup_distance = 0 + self._hover_popup = False + self._popdown_on_leave = False + + self.connect('popup', self._popup_cb) + self.connect('popdown', self._popdown_cb) + + def set_popup_distance(self, distance): + self._popup_distance = distance + + def get_friend(self): + return self._friend + + def _popdown(self): + if self._popup: + self._popup.destroy() + self._popup = None + + def _popup_cb(self, icon, x1, y1, x2, y2): + self._popdown() + + BuddyIcon._popup_shell.set_active(None) + + grid = self._shell.get_grid() + self._popup = BuddyPopup(self._shell, icon.get_friend()) + self._popup.connect('action', self._popup_action_cb) + self._popup.connect('enter-notify-event', + self._popup_enter_notify_event_cb) + self._popup.connect('leave-notify-event', + self._popup_leave_notify_event_cb) + + distance = self._popup_distance + + [grid_x1, grid_y1] = grid.convert_from_screen(x1, y1) + [grid_x2, grid_y2] = grid.convert_from_screen(x2, y2) + + grid_x = grid_x2 + distance + if grid_x + self._popup.get_width() > Grid.ROWS: + grid_x = grid_x1 - self._popup.get_width() + 1 - distance + + grid_y = grid_y1 + + if grid_y < 0: + grid_y = 0 + if grid_y + self._popup.get_width() > Grid.ROWS: + grid_y = Grid.ROWS - self._popup.get_width() + + grid.set_constraints(self._popup, grid_x, grid_y, + self._popup.get_width(), self._popup.get_height()) + + self._popup.show() + + BuddyIcon._popup_shell.set_active(self) + + def _popup_action_cb(self, popup, action): + self._popdown() + + buddy = self._friend.get_buddy() + if buddy == None: + return + + model = self._shell.get_model() + if action == BuddyPopup.ACTION_INVITE: + activity = model.get_current_activity() + activity.invite(buddy) + elif action == BuddyPopup.ACTION_MAKE_FRIEND: + friends = model.get_friends() + friends.make_friend(buddy) + elif action == BuddyPopup.ACTION_REMOVE_FRIEND: + friends = model.get_friends() + friends.remove(buddy) + + def _popdown_cb(self, friend): + if not self._hover_popup: + self._popdown() + else: + self._popdown_on_leave = True + + def _popup_enter_notify_event_cb(self, widget, event): + self._hover_popup = True + + def _popup_leave_notify_event_cb(self, widget, event): + self._hover_popup = False + if self._popdown_on_leave: + self._popdown() diff --git a/shell/FriendPopup.py b/shell/view/BuddyPopup.py index 1d309a6..4aab3f6 100644 --- a/shell/FriendPopup.py +++ b/shell/view/BuddyPopup.py @@ -1,21 +1,32 @@ import gtk import goocanvas +import gobject from sugar.canvas.CanvasView import CanvasView from sugar.canvas.CanvasBox import CanvasBox from sugar.canvas.IconItem import IconItem -class FriendPopup(gtk.Window): - def __init__(self, shell, grid, friend): +class BuddyPopup(gtk.Window): + ACTION_MAKE_FRIEND = 0 + ACTION_INVITE = 1 + ACTION_REMOVE_FRIEND = 2 + + __gsignals__ = { + 'action': (gobject.SIGNAL_RUN_FIRST, + gobject.TYPE_NONE, ([int])), + } + + def __init__(self, shell, buddy): gtk.Window.__init__(self, gtk.WINDOW_POPUP) - self._shell = shell - self._friend = friend + self._buddy = buddy self._hover = False self._popdown_on_leave = False self._width = 13 self._height = 10 + grid = shell.get_grid() + canvas = CanvasView() self.add(canvas) canvas.show() @@ -25,14 +36,14 @@ class FriendPopup(gtk.Window): model = goocanvas.CanvasModelSimple() root = model.get_root_item() - color = friend.get_color() + color = buddy.get_color() rect = goocanvas.Rect(fill_color=color.get_fill_color(), stroke_color=color.get_stroke_color(), line_width=3) grid.set_constraints(rect, 0, 0, self._width, self._height) root.add_child(rect) - text = goocanvas.Text(text=friend.get_name(), font="Sans bold 18", + text = goocanvas.Text(text=buddy.get_name(), font="Sans bold 18", fill_color='black', anchor=gtk.ANCHOR_SW) grid.set_constraints(text, 1, 3, self._width, self._height) root.add_child(text) @@ -45,8 +56,16 @@ class FriendPopup(gtk.Window): box = CanvasBox(grid, CanvasBox.HORIZONTAL, 1) grid.set_constraints(box, 0, 5) - icon = IconItem(icon_name='stock-make-friend') - icon.connect('clicked', self._make_friend_clicked_cb) + friends = shell.get_model().get_friends() + if friends.has_buddy(buddy): + icon = IconItem(icon_name='stock-remove-friend') + icon.connect('clicked', self._action_clicked_cb, + BuddyPopup.ACTION_REMOVE_FRIEND) + else: + icon = IconItem(icon_name='stock-make-friend') + icon.connect('clicked', self._action_clicked_cb, + BuddyPopup.ACTION_MAKE_FRIEND) + box.set_constraints(icon, 3, 3) box.add_child(icon) @@ -55,7 +74,8 @@ class FriendPopup(gtk.Window): box.add_child(icon) icon = IconItem(icon_name='stock-invite') - icon.connect('clicked', self._invite_clicked_cb) + icon.connect('clicked', self._action_clicked_cb, + BuddyPopup.ACTION_INVITE) box.set_constraints(icon, 3, 3) box.add_child(icon) @@ -63,33 +83,8 @@ class FriendPopup(gtk.Window): canvas.set_model(model) - self.connect('enter-notify-event', self._enter_notify_event_cb) - self.connect('leave-notify-event', self._leave_notify_event_cb) - - def _invite_clicked_cb(self, icon): - activity = self._shell.get_current_activity() - buddy = self._friend.get_buddy() - if buddy != None: - activity.invite(buddy) - else: - print 'Friend not online' - - def _make_friend_clicked_cb(self, icon): - pass - - def _enter_notify_event_cb(self, widget, event): - self._hover = True - - def _leave_notify_event_cb(self, widget, event): - self._hover = False - if self._popdown_on_leave: - self.popdown() - - def popdown(self): - if not self._hover: - self.destroy() - else: - self._popdown_on_leave = True + def _action_clicked_cb(self, icon, action): + self.emit('action', action) def get_width(self): return self._width diff --git a/shell/ConsoleWindow.py b/shell/view/ConsoleWindow.py index 4cdbbb1..4cdbbb1 100644 --- a/shell/ConsoleWindow.py +++ b/shell/view/ConsoleWindow.py diff --git a/shell/FirstTimeDialog.py b/shell/view/FirstTimeDialog.py index abf6bd8..abf6bd8 100644 --- a/shell/FirstTimeDialog.py +++ b/shell/view/FirstTimeDialog.py diff --git a/shell/view/Makefile.am b/shell/view/Makefile.am new file mode 100644 index 0000000..4b1c901 --- /dev/null +++ b/shell/view/Makefile.am @@ -0,0 +1,11 @@ +SUBDIRS = frame home + +sugardir = $(pkgdatadir)/shell/view +sugar_PYTHON = \ + __init__.py \ + ActivityHost.py \ + ConsoleWindow.py \ + FirstTimeDialog.py \ + BuddyIcon.py \ + BuddyPopup.py \ + Shell.py diff --git a/shell/view/Shell.py b/shell/view/Shell.py new file mode 100644 index 0000000..76d3f26 --- /dev/null +++ b/shell/view/Shell.py @@ -0,0 +1,102 @@ +import gtk +import gobject +import wnck + +from sugar.canvas.Grid import Grid +from view.home.HomeWindow import HomeWindow +from sugar.presence import PresenceService +from view.ActivityHost import ActivityHost +from sugar.activity import ActivityFactory +from sugar.activity import Activity +from view.frame.Frame import Frame +from globalkeys import KeyGrabber +import sugar + +class Shell(gobject.GObject): + def __init__(self, model): + gobject.GObject.__init__(self) + + self._model = model + self._screen = wnck.screen_get_default() + self._grid = Grid() + + self._key_grabber = KeyGrabber() + self._key_grabber.connect('key-pressed', self.__global_key_pressed_cb) + self._key_grabber.grab('F1') + self._key_grabber.grab('F2') + self._key_grabber.grab('F3') + self._key_grabber.grab('F4') + self._key_grabber.grab('F5') + self._key_grabber.grab('F6') + + self._home_window = HomeWindow(self) + self._home_window.show() + self.set_zoom_level(sugar.ZOOM_HOME) + + self._screen.connect('window-opened', self.__window_opened_cb) + self._screen.connect('window-closed', self.__window_closed_cb) + self._screen.connect('active-window-changed', + self.__active_window_changed_cb) + + self._frame = Frame(self) + self._frame.show_and_hide(10) + + def __global_key_pressed_cb(self, grabber, key): + if key == 'F1': + self.set_zoom_level(sugar.ZOOM_ACTIVITY) + elif key == 'F2': + self.set_zoom_level(sugar.ZOOM_HOME) + elif key == 'F3': + self.set_zoom_level(sugar.ZOOM_FRIENDS) + elif key == 'F4': + self.set_zoom_level(sugar.ZOOM_MESH) + elif key == 'F5': + self._frame.toggle_visibility() + elif key == 'F6': + self._model.start_activity('org.sugar.Terminal') + + def __window_opened_cb(self, screen, window): + if window.get_window_type() == wnck.WINDOW_NORMAL: + self._model.add_activity(ActivityHost(self, window)) + + def __active_window_changed_cb(self, screen): + window = screen.get_active_window() + if window and window.get_window_type() == wnck.WINDOW_NORMAL: + self._model.set_current_activity(window.get_xid()) + + def __window_closed_cb(self, screen, window): + if window.get_window_type() == wnck.WINDOW_NORMAL: + self._model.remove_activity(window.get_xid()) + + def get_model(self): + return self._model + + def get_grid(self): + return self._grid + + def join_activity(self, bundle_id, activity_id): + pservice = PresenceService.get_instance() + + activity = self._model.get_activity(activity_id) + if activity: + activity.present() + else: + activity_ps = pservice.get_activity(activity_id) + + if activity_ps: + activity = ActivityFactory.create(bundle_id) + activity.join(activity_ps.object_path()) + else: + logging.error('Cannot start activity.') + + def start_activity(self, activity_type): + activity = ActivityFactory.create(activity_type) + activity.execute('test', []) + return activity + + def set_zoom_level(self, level): + if level == sugar.ZOOM_ACTIVITY: + self._screen.toggle_showing_desktop(False) + else: + self._screen.toggle_showing_desktop(True) + self._home_window.set_zoom_level(level) diff --git a/shell/frame/__init__.py b/shell/view/__init__.py index e69de29..e69de29 100644 --- a/shell/frame/__init__.py +++ b/shell/view/__init__.py diff --git a/shell/frame/BottomPanel.py b/shell/view/frame/BottomPanel.py index 2da51e7..5f98f01 100644 --- a/shell/frame/BottomPanel.py +++ b/shell/view/frame/BottomPanel.py @@ -33,22 +33,22 @@ class InviteItem(IconItem): return self._invite class BottomPanel(CanvasBox): - def __init__(self, grid, shell, invites): - CanvasBox.__init__(self, grid, CanvasBox.HORIZONTAL, 1) + def __init__(self, shell): + CanvasBox.__init__(self, shell.get_grid(), CanvasBox.HORIZONTAL, 1) self._shell = shell self._invite_to_item = {} - self._invites = invites + self._invites = self._shell.get_model().get_invites() registry = conf.get_activity_registry() for activity in registry.list_activities(): if activity.get_show_launcher(): self.add_activity(activity) - for invite in invites: + for invite in self._invites: self.add_invite(invite) - invites.connect('invite-added', self.__invite_added_cb) - invites.connect('invite-removed', self.__invite_removed_cb) + self._invites.connect('invite-added', self.__invite_added_cb) + self._invites.connect('invite-removed', self.__invite_removed_cb) def __activity_clicked_cb(self, icon): self._shell.start_activity(icon.get_bundle_id()) diff --git a/shell/frame/Frame.py b/shell/view/frame/Frame.py index 1da50f0..9967a50 100644 --- a/shell/frame/Frame.py +++ b/shell/view/frame/Frame.py @@ -2,39 +2,39 @@ import gtk import gobject import goocanvas -from frame.BottomPanel import BottomPanel -from frame.RightPanel import RightPanel -from frame.TopPanel import TopPanel -from frame.PanelWindow import PanelWindow +from view.frame.BottomPanel import BottomPanel +from view.frame.RightPanel import RightPanel +from view.frame.TopPanel import TopPanel +from view.frame.PanelWindow import PanelWindow from sugar.canvas.Grid import Grid class Frame: - def __init__(self, shell, owner): + def __init__(self, shell): self._windows = [] model = goocanvas.CanvasModelSimple() root = model.get_root_item() - grid = Grid() + grid = shell.get_grid() bg = goocanvas.Rect(fill_color="#4f4f4f", line_width=0) grid.set_constraints(bg, 0, 0, 80, 60) root.add_child(bg) - panel = BottomPanel(grid, shell, owner.get_invites()) + panel = BottomPanel(shell) grid.set_constraints(panel, 5, 55) root.add_child(panel) panel_window = PanelWindow(grid, model, 0, 55, 80, 5) self._windows.append(panel_window) - panel = TopPanel(grid, shell) + panel = TopPanel(shell) root.add_child(panel) panel_window = PanelWindow(grid, model, 0, 0, 80, 5) self._windows.append(panel_window) - panel = RightPanel(grid, shell, owner.get_friends()) + panel = RightPanel(shell) grid.set_constraints(panel, 75, 5) root.add_child(panel) diff --git a/shell/frame/Makefile.am b/shell/view/frame/Makefile.am index 9f45543..a737e01 100644 --- a/shell/frame/Makefile.am +++ b/shell/view/frame/Makefile.am @@ -1,4 +1,4 @@ -sugardir = $(pkgdatadir)/shell/frame +sugardir = $(pkgdatadir)/shell/view/frame sugar_PYTHON = \ __init__.py \ RightPanel.py \ diff --git a/shell/frame/PanelWindow.py b/shell/view/frame/PanelWindow.py index 549776f..549776f 100644 --- a/shell/frame/PanelWindow.py +++ b/shell/view/frame/PanelWindow.py diff --git a/shell/frame/RightPanel.py b/shell/view/frame/RightPanel.py index 80729fd..6ed4a0d 100644 --- a/shell/frame/RightPanel.py +++ b/shell/view/frame/RightPanel.py @@ -4,12 +4,13 @@ from sugar.canvas.IconItem import IconItem from sugar.canvas.IconColor import IconColor from sugar.canvas.CanvasBox import CanvasBox from sugar.presence import PresenceService +from view.BuddyIcon import BuddyIcon +from model.BuddyInfo import BuddyInfo class RightPanel(CanvasBox): - def __init__(self, grid, shell, friends): - CanvasBox.__init__(self, grid, CanvasBox.VERTICAL, 1) + def __init__(self, shell): + CanvasBox.__init__(self, shell.get_grid(), CanvasBox.VERTICAL, 1) self._shell = shell - self._friends = friends self._activity_ps = None self._joined_hid = -1 self._left_hid = -1 @@ -19,14 +20,13 @@ class RightPanel(CanvasBox): self._pservice.connect('activity-appeared', self.__activity_appeared_cb) - shell.connect('activity-changed', self.__activity_changed_cb) + shell.get_model().connect('activity-changed', + self.__activity_changed_cb) def add(self, buddy): - icon = IconItem(icon_name='stock-buddy', - color=IconColor(buddy.get_color())) + icon = BuddyIcon(self._shell, BuddyInfo(buddy)) + icon.set_popup_distance(1) self.set_constraints(icon, 3, 3) - icon.connect('clicked', self.__buddy_clicked_cb, buddy) - self.add_child(icon) self._buddies[buddy.get_name()] = icon @@ -41,7 +41,7 @@ class RightPanel(CanvasBox): self._buddies = {} def __activity_appeared_cb(self, pservice, activity_ps): - activity = self._shell.get_current_activity() + activity = self._shell.get_model().get_current_activity() if activity and activity_ps.get_id() == activity.get_id(): self._set_activity_ps(activity_ps) @@ -78,6 +78,3 @@ class RightPanel(CanvasBox): def __buddy_left_cb(self, activity, buddy): self.remove(buddy) - - def __buddy_clicked_cb(self, icon, buddy): - self._friends.add_buddy(buddy) diff --git a/shell/frame/TopPanel.py b/shell/view/frame/TopPanel.py index 14c0fb7..b1611d7 100644 --- a/shell/frame/TopPanel.py +++ b/shell/view/frame/TopPanel.py @@ -5,14 +5,15 @@ from sugar.canvas.IconItem import IconItem import sugar class TopPanel(goocanvas.Group): - def __init__(self, grid, shell): + def __init__(self, shell): goocanvas.Group.__init__(self) - self._grid = grid self._shell = shell + grid = shell.get_grid() + box = CanvasBox(grid, CanvasBox.HORIZONTAL, 1) - self._grid.set_constraints(box, 5, 0) + grid.set_constraints(box, 5, 0) self.add_child(box) icon = IconItem(icon_name='stock-zoom-activity') @@ -36,7 +37,7 @@ class TopPanel(goocanvas.Group): box.add_child(icon) box = CanvasBox(grid, CanvasBox.HORIZONTAL, 1) - self._grid.set_constraints(box, 60, 0) + grid.set_constraints(box, 60, 0) self.add_child(box) icon = IconItem(icon_name='stock-share') @@ -58,7 +59,8 @@ class TopPanel(goocanvas.Group): self._shell.set_zoom_level(level) def __share_clicked_cb(self, item): - activity = self._shell.get_current_activity() + shell_model = self._shell.get_model() + activity = shell_model.get_current_activity() if activity != None: activity.share() diff --git a/shell/home/__init__.py b/shell/view/frame/__init__.py index e69de29..e69de29 100644 --- a/shell/home/__init__.py +++ b/shell/view/frame/__init__.py diff --git a/shell/home/DonutItem.py b/shell/view/home/DonutItem.py index 9038523..9038523 100644 --- a/shell/home/DonutItem.py +++ b/shell/view/home/DonutItem.py diff --git a/shell/view/home/FriendsGroup.py b/shell/view/home/FriendsGroup.py new file mode 100644 index 0000000..1749738 --- /dev/null +++ b/shell/view/home/FriendsGroup.py @@ -0,0 +1,42 @@ +import random + +import goocanvas + +from view.home.IconLayout import IconLayout +from view.home.MyIcon import MyIcon +from view.BuddyIcon import BuddyIcon + +class FriendsGroup(goocanvas.Group): + def __init__(self, shell): + goocanvas.Group.__init__(self) + + self._shell = shell + self._icon_layout = IconLayout(1200, 900) + self._friends = {} + + me = MyIcon(100) + me.translate(600 - (me.get_property('size') / 2), + 450 - (me.get_property('size') / 2)) + self.add_child(me) + + friends = self._shell.get_model().get_friends() + + for friend in friends: + self.add_friend(friend) + + friends.connect('friend-added', self._friend_added_cb) + friends.connect('friend-removed', self._friend_removed_cb) + + def add_friend(self, buddy_info): + icon = BuddyIcon(self._shell, buddy_info) + self.add_child(icon) + self._icon_layout.add_icon(icon) + + self._friends[buddy_info.get_name()] = icon + + def _friend_added_cb(self, data_model, buddy_info): + self.add_friend(buddy_info) + + def _friend_removed_cb(self, data_model, name): + self.remove_child(self._friends[name]) + del self._friends[name] diff --git a/shell/home/HomeGroup.py b/shell/view/home/HomeGroup.py index 0f08813..d01ba16 100644 --- a/shell/home/HomeGroup.py +++ b/shell/view/home/HomeGroup.py @@ -1,7 +1,7 @@ import goocanvas -from home.DonutItem import DonutItem -from home.MyIcon import MyIcon +from view.home.DonutItem import DonutItem +from view.home.MyIcon import MyIcon class TasksItem(DonutItem): def __init__(self, shell): @@ -9,14 +9,14 @@ class TasksItem(DonutItem): self._items = {} - self._shell = shell - self._shell.connect('activity_opened', self.__activity_opened_cb) - self._shell.connect('activity_closed', self.__activity_closed_cb) + shell_model = shell.get_model() + shell_model.connect('activity_opened', self.__activity_opened_cb) + shell_model.connect('activity_closed', self.__activity_closed_cb) - def __activity_opened_cb(self, shell, activity): + def __activity_opened_cb(self, model, activity): self._add(activity) - def __activity_closed_cb(self, shell, activity): + def __activity_closed_cb(self, model, activity): self._remove(activity) def _remove(self, activity): diff --git a/shell/home/HomeWindow.py b/shell/view/home/HomeWindow.py index 259ddc4..ba1129e 100644 --- a/shell/home/HomeWindow.py +++ b/shell/view/home/HomeWindow.py @@ -3,9 +3,9 @@ import goocanvas import cairo from sugar.canvas.CanvasView import CanvasView -from home.MeshGroup import MeshGroup -from home.HomeGroup import HomeGroup -from home.FriendsGroup import FriendsGroup +from view.home.MeshGroup import MeshGroup +from view.home.HomeGroup import HomeGroup +from view.home.FriendsGroup import FriendsGroup import sugar class HomeWindow(gtk.Window): @@ -23,6 +23,10 @@ class HomeWindow(gtk.Window): self.add(self._nb) self._nb.show() + self._add_page(HomeGroup(shell)) + self._add_page(FriendsGroup(shell)) + self._add_page(MeshGroup(shell)) + def _add_page(self, group): view = CanvasView() self._nb.append_page(view) @@ -37,11 +41,6 @@ class HomeWindow(gtk.Window): root.add_child(bg) root.add_child(group) - def set_owner(self, owner): - self._add_page(HomeGroup(self._shell)) - self._add_page(FriendsGroup(self._shell, owner.get_friends())) - self._add_page(MeshGroup(self._shell)) - def set_zoom_level(self, level): if level == sugar.ZOOM_HOME: self._nb.set_current_page(0) diff --git a/shell/home/IconLayout.py b/shell/view/home/IconLayout.py index eedced8..eedced8 100644 --- a/shell/home/IconLayout.py +++ b/shell/view/home/IconLayout.py diff --git a/shell/home/Makefile.am b/shell/view/home/Makefile.am index 5d5436e..ddf9653 100644 --- a/shell/home/Makefile.am +++ b/shell/view/home/Makefile.am @@ -1,4 +1,4 @@ -sugardir = $(pkgdatadir)/shell/home +sugardir = $(pkgdatadir)/shell/view/home sugar_PYTHON = \ __init__.py \ DonutItem.py \ diff --git a/shell/home/MeshGroup.py b/shell/view/home/MeshGroup.py index f7bc1c9..6111823 100644 --- a/shell/home/MeshGroup.py +++ b/shell/view/home/MeshGroup.py @@ -6,7 +6,7 @@ import conf from sugar.canvas.IconItem import IconItem from sugar.canvas.IconItem import IconColor from sugar.presence import PresenceService -from home.IconLayout import IconLayout +from view.home.IconLayout import IconLayout class ActivityItem(IconItem): def __init__(self, activity, service): @@ -34,7 +34,9 @@ class ActivityItem(IconItem): class MeshGroup(goocanvas.Group): def __init__(self, shell): goocanvas.Group.__init__(self) + self._shell = shell + self._icon_layout = IconLayout(1200, 900) self._activities = {} diff --git a/shell/home/MyIcon.py b/shell/view/home/MyIcon.py index 226520d..226520d 100644 --- a/shell/home/MyIcon.py +++ b/shell/view/home/MyIcon.py diff --git a/shell/PresenceService/__init__.py b/shell/view/home/__init__.py index e69de29..e69de29 100644 --- a/shell/PresenceService/__init__.py +++ b/shell/view/home/__init__.py |