Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/shell
diff options
context:
space:
mode:
authorDan Williams <dcbw@localhost.localdomain>2006-09-15 20:50:06 (GMT)
committer Dan Williams <dcbw@localhost.localdomain>2006-09-15 20:50:06 (GMT)
commit922b7238b9db8307f6b9df9b3a4e36f9a46e81a0 (patch)
tree90aa62656494be7b5740c43549adbbcfaab49ac8 /shell
parentb39eff33655590565aaac3d514df640f1ad3f2a8 (diff)
parente79a5f5e989fa062366295c3f236692fc3c889e5 (diff)
Merge
Diffstat (limited to 'shell')
-rw-r--r--shell/ChatController.py54
-rw-r--r--shell/Makefile.am14
-rw-r--r--shell/PresenceService/Activity.py171
-rw-r--r--shell/PresenceService/Buddy.py402
-rw-r--r--shell/PresenceService/Makefile.am18
-rw-r--r--shell/PresenceService/PresenceService.py785
-rw-r--r--shell/PresenceService/Service.py488
-rw-r--r--shell/PresenceService/org.laptop.Presence.service.in4
-rw-r--r--shell/Session.py13
-rwxr-xr-xshell/Shell.py174
-rw-r--r--shell/home/FriendsGroup.py84
-rw-r--r--shell/model/BuddyInfo.py24
-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.am8
-rw-r--r--shell/model/Owner.py (renamed from shell/Owner.py)8
-rw-r--r--shell/model/ShellModel.py64
-rw-r--r--shell/model/__init__.py (copied from shell/PresenceService/__init__.py)0
-rwxr-xr-xshell/sugar-presence-service11
-rw-r--r--shell/view/ActivityHost.py (renamed from shell/ActivityHost.py)3
-rw-r--r--shell/view/BuddyIcon.py108
-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.am11
-rw-r--r--shell/view/Shell.py102
-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.py42
-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