235 lines
8.5 KiB
Python
235 lines
8.5 KiB
Python
# Copyright (C) 2006, Red Hat, Inc.
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
import gtk
|
|
import gobject
|
|
import os
|
|
import logging
|
|
|
|
from sugar.p2p import MostlyReliablePipe
|
|
from sugar.p2p.Stream import Stream
|
|
|
|
from sugar.presence import PresenceService
|
|
from sugar.activity.Activity import Activity
|
|
from sugar.chat.sketchpad import SketchPad
|
|
from sugar.chat.sketchpad import Sketch
|
|
from sugar.graphics.iconcolor import IconColor
|
|
from sugar import profile
|
|
|
|
class NetworkController(gobject.GObject):
|
|
__gsignals__ = {
|
|
'new-path':(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
|
|
}
|
|
|
|
def __init__(self, parent, ps_owner):
|
|
gobject.GObject.__init__(self)
|
|
self._parent = parent
|
|
self._parent.connect('buddy-joined', self._buddy_joined)
|
|
self._parent.connect('buddy-left', self._buddy_left)
|
|
self._stream = None
|
|
self._stream_writer = None
|
|
self._joined_buddies = {} # IP address -> buddy
|
|
self._ps_owner = ps_owner
|
|
|
|
def init_stream(self, service):
|
|
self._stream = Stream.new_from_service(service)
|
|
self._stream.set_data_listener(self._recv_message)
|
|
self._stream_writer = self._stream.new_writer()
|
|
|
|
def _recv_message(self, address, msg):
|
|
# Ignore multicast messages from ourself
|
|
if self._ps_owner and address == self._ps_owner.get_ip4_address():
|
|
return
|
|
|
|
# Ensure the message comes from somebody in this activity
|
|
if not self._joined_buddies.has_key(address):
|
|
logging.debug("Message from unjoined buddy.")
|
|
return
|
|
|
|
# Convert the points to an array and send to the sketchpad
|
|
points = []
|
|
msg = msg.strip()
|
|
split_coords = msg.split(" ")
|
|
for item in split_coords:
|
|
x = 0
|
|
y = 0
|
|
try:
|
|
(x, y) = item.split(",")
|
|
x = float(x)
|
|
y = float(y)
|
|
except ValueError:
|
|
continue
|
|
if x < 0 or y < 0:
|
|
continue
|
|
points.append((x, y))
|
|
|
|
buddy = self._joined_buddies[address]
|
|
self.emit("new-path", buddy, points)
|
|
|
|
def _buddy_joined(self, widget, activity, buddy, activity_type):
|
|
activity_service = buddy.get_service_of_type(activity_type, activity)
|
|
if not activity_service:
|
|
logging.debug("Buddy Joined, but could not get activity service " \
|
|
"of %s" % activity_type)
|
|
return
|
|
|
|
address = activity_service.get_source_address()
|
|
port = activity_service.get_port()
|
|
if not address or not port:
|
|
logging.debug("Buddy Joined, but could not get address/port from" \
|
|
" activity service %s" % activity_type)
|
|
return
|
|
if not self._joined_buddies.has_key(address):
|
|
logging.debug("Buddy joined: %s (%s)" % (address, port))
|
|
self._joined_buddies[address] = buddy
|
|
|
|
def _buddy_left(self, widget, activity, buddy, activity_type):
|
|
buddy_key = None
|
|
for (key, value) in self._joined_buddies.items():
|
|
if value == buddy:
|
|
buddy_key = key
|
|
break
|
|
if buddy_key:
|
|
del self._joined_buddies[buddy_key]
|
|
|
|
def new_local_sketch(self, path):
|
|
""" Receive an array of point tuples the local user created """
|
|
cmd = ""
|
|
# Convert points into the wire format
|
|
for point in path:
|
|
cmd = cmd + "%d,%d " % (point[0], point[1])
|
|
|
|
# If there were no points, or we aren't in a shared activity yet,
|
|
# don't send anything
|
|
if not len(cmd) or not self._stream_writer:
|
|
return
|
|
|
|
# Send the points to other buddies
|
|
self._stream_writer.write(cmd)
|
|
|
|
def _html_to_rgb_color(colorstring):
|
|
""" converts #RRGGBB to cairo-suitable floats"""
|
|
colorstring = colorstring.strip()
|
|
while colorstring[0] == '#':
|
|
colorstring = colorstring[1:]
|
|
r = int(colorstring[:2], 16)
|
|
g = int(colorstring[2:4], 16)
|
|
b = int(colorstring[4:6], 16)
|
|
color = ((float(r) / 255.0), (float(g) / 255.0), (float(b) / 255.0))
|
|
return color
|
|
|
|
|
|
class SharedSketchPad(SketchPad.SketchPad):
|
|
def __init__(self, net_controller, color):
|
|
SketchPad.SketchPad.__init__(self, bgcolor=(1.0, 0.984313725, 0.560784314))
|
|
self._net_controller = net_controller
|
|
self._user_color = _html_to_rgb_color(color)
|
|
self.set_color(self._user_color)
|
|
|
|
# Receive notifications when our buddies send us new sketches
|
|
self._net_controller.connect('new-path', self._new_buddy_path)
|
|
|
|
self.connect('new-user-sketch', self._new_local_sketch_cb)
|
|
|
|
def _new_buddy_path(self, net_controller, buddy, path):
|
|
""" Called whenever a buddy on the mesh sends us a new sketch path """
|
|
str_color = buddy.get_color()
|
|
if not str_color:
|
|
str_color = "#348798" # FIXME
|
|
color = IconColor(str_color)
|
|
stroke_color = _html_to_rgb_color(color.get_stroke_color())
|
|
sketch = Sketch.Sketch(stroke_color)
|
|
for item in path:
|
|
sketch.add_point(item[0], item[1])
|
|
self.add_sketch(sketch)
|
|
|
|
def _new_local_sketch_cb(self, widget, sketch):
|
|
""" Send the sketch the user just made to the network """
|
|
self._net_controller.new_local_sketch(sketch.get_points())
|
|
|
|
|
|
class SketchActivity(Activity):
|
|
__gsignals__ = {
|
|
'buddy-joined':(gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
|
|
'buddy-left': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
|
|
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
|
|
}
|
|
|
|
def __init__(self):
|
|
Activity.__init__(self)
|
|
self.connect('destroy', self._cleanup_cb)
|
|
|
|
self.set_title("Sketch")
|
|
|
|
self._ps = PresenceService.get_instance()
|
|
self._ps_activity = None
|
|
self._owner = self._ps.get_owner()
|
|
|
|
self._net_controller = NetworkController(self, self._owner)
|
|
self._sketchpad = SharedSketchPad(self._net_controller,
|
|
profile.get_color().get_stroke_color())
|
|
self.add(self._sketchpad)
|
|
self.show_all()
|
|
|
|
def get_ps(self):
|
|
return self._ps
|
|
|
|
def _cleanup_cb(self):
|
|
del self._net_controller
|
|
|
|
def share(self):
|
|
Activity.share(self)
|
|
self._net_controller.init_stream(self._service)
|
|
self._ps.connect('activity-appeared', self._activity_appeared_cb)
|
|
|
|
def join(self, activity_ps):
|
|
Activity.join(self, activity_ps)
|
|
self._net_controller.init_stream(self._service)
|
|
self._ps.connect('activity-appeared', self._activity_appeared_cb)
|
|
self._activity_appeared_cb(self._ps, activity_ps)
|
|
|
|
def _activity_appeared_cb(self, ps, activity):
|
|
# Only care about our own activity
|
|
if activity.get_id() != self.get_id():
|
|
return
|
|
|
|
# If we already have found our shared activity, do nothing
|
|
if self._ps_activity:
|
|
return
|
|
|
|
self._ps_activity = activity
|
|
|
|
# Connect signals to the shared activity so we are notified when
|
|
# buddies join and leave
|
|
self._ps_activity.connect('buddy-joined', self._add_buddy)
|
|
self._ps_activity.connect('buddy-left', self._remove_buddy)
|
|
|
|
# Get the list of buddies already in this shared activity so we can
|
|
# connect to them
|
|
buddies = self._ps_activity.get_joined_buddies()
|
|
for buddy in buddies:
|
|
self._add_buddy(self._ps_activity, buddy)
|
|
|
|
def _add_buddy(self, ps_activity, buddy):
|
|
service_type = self._ps_activity
|
|
self.emit('buddy-joined', ps_activity, buddy, self.get_default_type())
|
|
|
|
def _remove_buddy(self, ps_activity, buddy):
|
|
self.emit('buddy-left', ps_activity, buddy, self.get_default_type())
|
|
|