This commit is contained in:
Dan Williams
2006-09-15 16:50:06 -04:00
54 changed files with 519 additions and 483 deletions
-54
View File
@@ -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])
+2 -12
View File
@@ -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)
-171
View File
@@ -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())
-402
View File
@@ -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()
-18
View File
@@ -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)
-785
View File
@@ -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()
-488
View File
@@ -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()
@@ -1,4 +0,0 @@
[D-BUS Service]
Name = org.laptop.Presence
Exec = @bindir@/sugar-presence-service
+5 -8
View File
@@ -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()
-174
View File
@@ -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)
-84
View File
@@ -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)
+24
View File
@@ -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)
+19 -32
View File
@@ -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()
+8
View File
@@ -0,0 +1,8 @@
sugardir = $(pkgdatadir)/shell/model
sugar_PYTHON = \
__init__.py \
BuddyInfo.py \
Friends.py \
Invites.py \
Owner.py \
ShellModel.py
+2 -6
View File
@@ -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
+64
View File
@@ -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
-11
View File
@@ -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()
@@ -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
+108
View File
@@ -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()
@@ -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
+11
View File
@@ -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
+102
View File
@@ -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)
@@ -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())
@@ -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)
@@ -1,4 +1,4 @@
sugardir = $(pkgdatadir)/shell/frame
sugardir = $(pkgdatadir)/shell/view/frame
sugar_PYTHON = \
__init__.py \
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)
@@ -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()
+42
View File
@@ -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]
@@ -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):
@@ -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)
@@ -1,4 +1,4 @@
sugardir = $(pkgdatadir)/shell/home
sugardir = $(pkgdatadir)/shell/view/home
sugar_PYTHON = \
__init__.py \
DonutItem.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 = {}
View File