Merge
This commit is contained in:
@@ -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
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
@@ -0,0 +1,8 @@
|
||||
sugardir = $(pkgdatadir)/shell/model
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
BuddyInfo.py \
|
||||
Friends.py \
|
||||
Invites.py \
|
||||
Owner.py \
|
||||
ShellModel.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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
Reference in New Issue
Block a user