Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbens <bens@bemasc.localdomain>2009-12-09 17:38:04 (GMT)
committer bens <bens@bemasc.localdomain>2009-12-09 17:38:04 (GMT)
commit43445d6a39c1c350e604b795aa78adac7374124c (patch)
tree62a9921e2c84c2a6099d56908200966a21fd1fa9
Initial Import
-rw-r--r--MAINTAINERS1
-rw-r--r--NEWS4
-rw-r--r--README11
-rw-r--r--README.ldshim18
-rw-r--r--activity/activity-watchme.svg102
-rw-r--r--activity/activity.info9
-rwxr-xr-xldshim.py53
-rwxr-xr-xldshim_example.py3
-rwxr-xr-xsetup.py22
-rw-r--r--watchme.py182
10 files changed, 405 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
new file mode 100644
index 0000000..7d1f84e
--- /dev/null
+++ b/MAINTAINERS
@@ -0,0 +1 @@
+Ben Schwartz (bens@alum.mit.edu)
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..b61c9e7
--- /dev/null
+++ b/NEWS
@@ -0,0 +1,4 @@
+v2: Include gtk-vnc in the bundle using the shim layer. Bundled versions are:
+gtk-vnc-0.3.8-2.fc9.i386.rpm
+gtk-vnc-python-0.3.8-2.fc9.i386.rpm
+http://downloads.sourceforge.net/x11vnc/x11vnc-0.9.8_i386-none-linux
diff --git a/README b/README
new file mode 100644
index 0000000..9589107
--- /dev/null
+++ b/README
@@ -0,0 +1,11 @@
+Watch Me lets you share a view of your screen with anyone. It does this by running
+a VNC server (x11vnc) on the initiator machine, and running a VNC client (based on
+gtk-vnc-python) on the client.
+
+Watch Me depends on gtk-vnc-python and x11vnc. Copies of these programs are included
+in the bundle; however, for maximum compatibility, you should install these programs
+using your system's package manager.
+
+To build a Watch Me bundle including binaries, it is necessary to install gtk-vnc,
+ gtk-vnc-python, and x11vnc. Current bundles are constructed from RPMs of the first two
+and a static binary of the third, using the process described in README.ldshim
diff --git a/README.ldshim b/README.ldshim
new file mode 100644
index 0000000..9779623
--- /dev/null
+++ b/README.ldshim
@@ -0,0 +1,18 @@
+Normally, one would add libraries and python modules to a bundle using LD_LIBRARY_PATH
+and PYTHONPATH. This gives them priority over the systemwide versions of these items.
+However, this is a dangerous approach, as if the included versions are incompatible with
+the system, the program will not run. ldshim.py creates a safer alternative, by adding
+the bundled binaries to the _end_ of the search path, rather than the beginning, so that
+systemwide binaries take precedence.
+
+To produce a bundle from RPMs, one must download them into the bundle path
+and then execute
+
+cat [name].rpm | rpm2cpio | cpio -di
+
+for each RPM. This will produce a new root directory tree based on the
+current path. One may also need to add other binaries to the tree manually
+in directories like usr/bin/.
+
+If adding python modules, it is important to ensure that every directory
+recursively containing the python module is world-readable and executable.
diff --git a/activity/activity-watchme.svg b/activity/activity-watchme.svg
new file mode 100644
index 0000000..da2bafd
--- /dev/null
+++ b/activity/activity-watchme.svg
@@ -0,0 +1,102 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' [
+ <!ENTITY stroke_color "#010101">
+ <!ENTITY fill_color "#FFFFFF">
+]>
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Icon"
+ width="48.92"
+ height="43.846"
+ viewBox="0 0 48.92 43.846"
+ overflow="visible"
+ enable-background="new 0 0 48.92 43.846"
+ xml:space="preserve"
+ sodipodi:version="0.32"
+ inkscape:version="0.46"
+ sodipodi:docname="activity-watchme.svg"
+ sodipodi:docbase="/home/bens/olpc3d/range/AcousticMeasure.activity/activity"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"><metadata
+ id="metadata2238"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs2236"><inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 21.923 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="48.919998 : 21.923 : 1"
+ inkscape:persp3d-origin="24.459999 : 14.615334 : 1"
+ id="perspective2393" /></defs><sodipodi:namedview
+ inkscape:window-height="729"
+ inkscape:window-width="1016"
+ inkscape:pageshadow="2"
+ inkscape:pageopacity="0.0"
+ guidetolerance="10.0"
+ gridtolerance="10.0"
+ objecttolerance="10.0"
+ borderopacity="1.0"
+ bordercolor="#666666"
+ pagecolor="#ffffff"
+ id="base"
+ inkscape:zoom="9.2824886"
+ inkscape:cx="23.752045"
+ inkscape:cy="19.026166"
+ inkscape:window-x="0"
+ inkscape:window-y="29"
+ inkscape:current-layer="g3242"
+ showgrid="false" />
+
+
+
+
+
+
+
+
+<g
+ id="g3235"><g
+ id="g3242"><path
+ sodipodi:type="arc"
+ style="fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path3233"
+ sodipodi:cx="51.279354"
+ sodipodi:cy="25.801271"
+ sodipodi:rx="7.3256221"
+ sodipodi:ry="8.4567842"
+ d="M 58.604976,25.801271 A 7.3256221,8.4567842 0 1 1 43.953732,25.801271 A 7.3256221,8.4567842 0 1 1 58.604976,25.801271 z"
+ transform="matrix(0.9388262,-0.3443912,0.3443912,0.9388262,-18.665015,12.403548)" /><rect
+ style="fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:2.31399441;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect3227"
+ width="13.798601"
+ height="5.5034113"
+ x="-4.401123"
+ y="28.104681"
+ transform="matrix(0.9388262,-0.3443912,0.3443912,0.9388262,0,0)" /><rect
+ style="fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:2.11370969;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect3229"
+ width="18.846722"
+ height="11.090182"
+ x="7.6721964"
+ y="25.419024"
+ transform="matrix(0.9388262,-0.3443912,0.3443912,0.9388262,0,0)" /><rect
+ style="fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:1.91418922;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect3231"
+ width="10.427865"
+ height="18.399866"
+ x="23.408709"
+ y="21.65645"
+ transform="matrix(0.9388262,-0.3443912,0.3443912,0.9388262,0,0)" /><rect
+ style="fill:&fill_color;;fill-opacity:1;stroke:&stroke_color;;stroke-width:1.66383612;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect3225"
+ width="3.7835145"
+ height="8.9545441"
+ x="-7.6349049"
+ y="26.486843"
+ transform="matrix(0.9388262,-0.3443912,0.3443912,0.9388262,0,0)" /></g></g></svg>
diff --git a/activity/activity.info b/activity/activity.info
new file mode 100644
index 0000000..fd325cf
--- /dev/null
+++ b/activity/activity.info
@@ -0,0 +1,9 @@
+[Activity]
+name = Watch Me
+activity_version = 2
+service_name = org.sugarlabs.Watchme
+exec = ./ldshim.py sugar-activity watchme.WatchMeActivity
+icon = activity-watchme
+mime_types =
+license = GPLv2
+
diff --git a/ldshim.py b/ldshim.py
new file mode 100755
index 0000000..a219522
--- /dev/null
+++ b/ldshim.py
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+def parseconf(confpath):
+ import os.path
+ confdir = os.path.dirname(confpath)
+ f = open(confpath)
+ dirs = []
+ for line in f:
+ f = line.find('#') #strip comments
+ if f != -1:
+ line = line[:f]
+ f = line.find('=') #I don't know what = means, but binutils strips it
+ if f != -1:
+ line = line[:f]
+ line = line.strip() #strip whitespace
+ if line.startswith('include ') or line.startswith('include\t'):
+ includeglob = line[8:].strip()
+ if not includeglob.startswith(os.path.sep):
+ includeglob = os.path.join(confdir,includeglob)
+ import glob
+ for fname in glob.glob(includeglob):
+ dirs.extend(parseconf(fname))
+ elif line.startswith(os.path.sep):
+ dirs.append(line)
+ else:
+ dirs.append(os.path.join(confdir,line))
+ return dirs
+
+def getlinuxdirs():
+ dirs = ['/lib','/usr/lib']
+ dirs.extend(parseconf('/etc/ld.so.conf'))
+ return dirs
+
+def makepathstring(L):
+ return ':'.join(L)
+
+def addldpaths(dirs):
+ import os
+ paths = getlinuxdirs()
+ paths.extend(dirs)
+ os.environ['LD_LIBRARY_PATH'] = makepathstring(paths)
+
+if __name__ == "__main__":
+ import os
+ import sys
+ base = os.environ['SUGAR_BUNDLE_PATH']
+ print("The base path is %s" % base)
+ sys.path.append(os.path.join(base,'usr/lib/python2.5/site-packages'))
+ addldpaths([os.path.join(base,'usr/lib')]) #Sets LD_LIBRARY_PATH
+ os.environ['PYTHONPATH'] = ':'.join(sys.path) #Sets PYTHONPATH
+ os.environ['PATH'] += ":%s" % os.path.join(base,'usr/bin') #Extend PATH
+
+ import subprocess
+ subprocess.Popen(sys.argv[1:]) #Inherits the environment variables
diff --git a/ldshim_example.py b/ldshim_example.py
new file mode 100755
index 0000000..6ee0213
--- /dev/null
+++ b/ldshim_example.py
@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+import gtkvnc
+print gtkvnc.__file__
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..876cd3f
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,22 @@
+#!/usr/bin/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
+
+from sugar.activity import bundlebuilder
+
+bundlebuilder.start()
+
diff --git a/watchme.py b/watchme.py
new file mode 100644
index 0000000..38ecd8a
--- /dev/null
+++ b/watchme.py
@@ -0,0 +1,182 @@
+# Copyright (C) 2007, Eduardo Silva <edsiper@gmail.com>.
+# Copyright (C) 2008, One Laptop Per Child
+# Copyright (C) 2009, Ben Schwartz <bens@alum.mit.edu>
+#
+# 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 os
+
+import logging
+from gettext import gettext
+
+import gtk
+import dbus
+
+from sugar.activity import activity
+import os.path
+
+import telepathy
+import subprocess
+
+import signal
+
+SERVICE = 'org.sugarlabs.WatchMe'
+IFACE = SERVICE
+PATH = '/org/sugarlabs/WatchMe'
+
+class WatchMeActivity(activity.Activity):
+
+ def __init__(self, handle):
+ activity.Activity.__init__(self, handle)
+
+ self._logger = logging.getLogger('watchme-activity')
+ self._logger.debug('Starting the WatchMe activity')
+
+ self.set_title(gettext('WatchMe Activity'))
+
+ self._vncdaemon = None
+ self._vncviewer = None
+
+ if not self._shared_activity: #I am the initiator
+ toolbox = activity.ActivityToolbox(self)
+ activity_toolbar = toolbox.get_activity_toolbar()
+ activity_toolbar.keep.props.visible = False
+
+ self.set_toolbox(toolbox)
+ toolbox.show()
+
+ label = gtk.Label(gettext("If you want people to be able to see everything on your screen, invite them to this activity"))
+
+ self._connected = True
+ # The initiator is "connected" without actually connecting to the VNC
+ # server, because they are looking at the X server.
+ self.connect('shared', self._shared_cb)
+ else: # I am joining
+ #A GUI is not created for a joiner. Hopefully, the vncviewer window
+ #will be the first window created by the activity, and so will get
+ #a working icon, etc.
+ label = gtk.Label(gettext("Please wait while you are connected to the shared session"))
+ self._connected = False
+ if self.get_shared(): #Already joined for some reason
+ self._joined_cb()
+ else:
+ self.connect('joined', self._joined_cb)
+ self.set_canvas(label)
+ self.show_all()
+
+ def _sharing_setup(self):
+ params = {} #could be used in the future to indicate a reflector
+
+ bundle_path = activity.get_bundle_path()
+ port = 5900
+ # Start a VNC daemon at an automatically located free TCP port,
+ # scaling down by a factor of 2 with no blending (for efficiency)
+ # allowing viewers to view only, not control the cursor or keyboard
+ # allowing multiple viewers to join
+ # continue running indefinitely
+ # allow connections only from localhost
+ self._vncdaemon = subprocess.Popen(['x11vnc', #search the PATH
+ '-autoport',str(port),
+ '-viewonly',
+ '-shared',
+ '-forever',
+ '-localhost'], stdout=subprocess.PIPE)
+
+ # When run with -autoport, x11vnc finds an open port, opens it, and
+ # prints a line of the form
+ # PORT=5972
+ # to stdout.
+ x = self._vncdaemon.stdout.readline()
+ success = 'PORT='
+ if x[:len(success)] == success:
+ port = int(x[len(success):])
+ logging.debug('started VNC daemon successfully on port %d' % port)
+ else:
+ self._vncdaemon.terminate()
+ self._vncdaemon.wait()
+ self._logger.error('unable to find an open port!')
+ self.close()
+ return (params, port)
+
+ def _shared_cb(self, activity):
+ self._logger.debug('My activity was shared')
+ self.initiating = True
+ (params, port) = self._sharing_setup()
+
+ self._logger.debug('This is my activity: making a tube...')
+
+ address = ('127.0.0.1', dbus.UInt16(port))
+
+ tubes_chan = self._shared_activity.telepathy_tubes_chan
+ id = tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferStreamTube(
+ SERVICE, params, telepathy.SOCKET_ADDRESS_TYPE_IPV4, address,
+ telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0)
+
+ def _joined_cb(self, also_self):
+ tubes_chan = self._shared_activity.telepathy_tubes_chan
+
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
+ self._new_tube_cb)
+ tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
+ reply_handler=self._list_tubes_reply_cb,
+ error_handler=self._list_tubes_error_cb)
+
+ def _new_tube_cb(self, tube_id, initiator, tube_type, service, params, state):
+ self._logger.debug('New Tube')
+ if ((tube_type == telepathy.TUBE_TYPE_STREAM) and
+ (service == SERVICE) and (not self._connected)):
+ tubes_chan = self._shared_activity.telepathy_tubes_chan
+ iface = tubes_chan[telepathy.CHANNEL_TYPE_TUBES]
+ addr = iface.AcceptStreamTube(tube_id,
+ telepathy.SOCKET_ADDRESS_TYPE_IPV4,
+ telepathy.SOCKET_ACCESS_CONTROL_LOCALHOST, 0)
+
+ port = int(addr[1])
+ # additional properties are available from params
+
+ ## Start a VNC viewer pointed at the appropriate port.
+ #self._vncviewer = subprocess.Popen(['/usr/bin/vncviewer',
+ # 'localhost::%d' % port,
+ # '-ViewOnly',
+ # '-Shared'])
+ import gtkvnc
+ vncwidget = gtkvnc.Display()
+ self.set_canvas(vncwidget)
+ vncwidget.realize() # I don't know what this does
+ vncwidget.open_host('localhost',str(port))
+ self._connected = True
+ self.show_all()
+
+ def can_close(self):
+ #if self._vncviewer is not None:
+ # try:
+ # os.kill(self._vncviewer.pid,signal.SIGTERM)
+ # except:
+ # pass
+ # #self._vncviewer.terminate() #requires python 2.6
+ if self._vncdaemon is not None:
+ try:
+ os.kill(self._vncdaemon.pid,signal.SIGTERM)
+ except:
+ pass
+ #self._vncdaemon.terminate() #requires python 2.6
+ return True
+
+ def _list_tubes_reply_cb(self, tubes):
+ for tube_info in tubes:
+ self._new_tube_cb(*tube_info)
+
+ def _list_tubes_error_cb(self, e):
+ self._logger.error('ListTubes() failed: %s' % e)