Remember when I ranted at length about how Apple was letting Microsoft take the lead in collaborative features as simple as screen sharing?
Well, until they come to their senses and make Apple Remote Desktop (or the built-in VNC server) as simple to use as the Vista screen sharing feature, I decided to have a go at it myself.
I wanted an approach that:
- Let me share a Mac screen with anybody, on any platform
- Required the least possible amount of software at either end (no complex servers and no funky clients to install)
- Wasn't too sophisticated (the more features, the harder things are to use)
- Would work remotely, provided you have direct Internet access
And what comes with pretty much every OS on the planet? Well, a browser, of course.
What about VNC?
At first I considered setting up an HTML page on my Mac and have it serve the VNC Java applet, but the built-in Mac OS X VNC server is utterly brain-dead in terms of image encodings and crashed most of the applet versions I tried.
Then I realized that Java applets aren't the best thing to ensure cross-platform support (modern Windows and Linux require you to find and download a JVM, which severely raises the bar and kills the "instant sharing" approach).
Plus I started running across issues such as screen scaling (which not all VNC clients do properly, let alone the Java ones), pre-configuring the applets to do shared connections, etc., etc.
Furthermore, trying another VNC server like OSXvnc would be cheating, since it requires extra software to be installed on the serving Mac.
KISS Is Best
As usual, taking down complexity a peg makes for a better solution.
Browsers can render images, and images are readily obtainable on the Mac using the screencapture CLI command. I didn't need interactivity at all, so a solution that let me see a Mac's screen in a browser as an image was fine with me.
All that remained was the refresh interval, and I settled on 10 seconds because it's a nice compromise between the pace of slideshow presentations and live demos (where you need to see some sort of mouse movement and interaction).
Incidentally, people doing online/LAN-based presentations might want to take a look at Mouseposé - it helps people keep track of the cursor.
So I decided to write a small Python script to serve screenshots via HTTP, taking some care to ensure that the screenshots were evenly spaced in time and that a bunch of simultaneous requests wouldn't result in undue load on the serving Mac and several nearly-identical screenshots being served to clients.
What Your Audience Sees
The end result looks like this:
Can't be much simpler, huh? And the code (without any dependencies whatsoever, runnable on just about any Mac) is small enough to include verbatim right here, HTTP server and all.
Code
import os, socket, datetime, SimpleHTTPServer, SocketServer, StringIO IMAGE_PATH = '/tmp/screen.png' INTERVAL = 10 HTTP_DATE_FORMAT = "%a, %d %b %Y %H:%M:%S %Z" def screenCapture(path): """Take screenshot if necessary""" try: # See if the current screenshot is stale t = os.stat(path) if((datetime.datetime.now() - datetime.datetime.fromtimestamp(t.st_mtime)) < datetime.timedelta(seconds=INTERVAL)): return except OSError: # File was not found pass os.system('screencapture -xC ' + path) os.system('sips -Z 800 ' + path) def formatPage(): """Render HTML Page""" return """<html> <head> <title>Screen Sharing: %s</title> <script language="JavaScript"> counter = 0; function update() { document.getElementById("screenshot").src = "/screen.png?" + counter; self.setTimeout("update()",%s); counter++; } </script> <style> body { font-family: Lucida, Arial, "MS Trebuchet", sans-serif; } .info { background-color: #DDD; padding: 4px; } </style> </head> <body onload="update();"> <center> <img id="screenshot" src="/screen.png"><br><p class="info">Updated every %s seconds</p> </center> </body> </html>""" % (socket.gethostname(), INTERVAL * 1000, INTERVAL) class LocalRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): """Custom HTTP Request Handler""" def send_head(self): """Common handler for GET and HEAD requests""" if self.path == "/": self.send_response(200) self.send_header("Content-type","text/html") self.end_headers() return StringIO.StringIO(formatPage()) if self.path[:11] == "/screen.png": try: screenCapture(IMAGE_PATH) f = open(IMAGE_PATH,'rb') self.send_response(200) self.send_header("Content-type","image/png") self.send_header("Content-Length", str(os.fstat(f.fileno()).st_size)) mtime = datetime.datetime.fromtimestamp(os.fstat(f.fileno()).st_mtime) self.send_header("Last-Modified", mtime.strftime(HTTP_DATE_FORMAT)) expires = mtime + datetime.timedelta(seconds=INTERVAL) self.send_header("Expires", expires.strftime(HTTP_DATE_FORMAT)) self.end_headers() return f except IOError: pass self.send_error(404, "File not found") return None if __name__=='__main__': screenCapture(IMAGE_PATH) httpd = SocketServer.TCPServer(('',2000),LocalRequestHandler) httpd.serve_forever()
Usage
To use this, just save the above as, say, present.py, type python present.py at the terminal prompt, and access your Mac via port 2000 like the URL you see above (hostname:2000).
Obviously, you'll have to figure out and tell other people your IP address, open any firewalls, etc., etc.
If you're a complete networking newbie, please don't ask me how to find your own IP address. Learn the basics first...
Possible Enhancements
And, of course, this can be enhanced in all sorts of ways:
- Taking CLI parameters such as the port number, refresh interval and screen size
- Auto-detecting the most useful interface (usually en0 or en1 in Macs) and outputting the IP address upon startup
- It can be re-packaged as an application bundle
- It can use PyObjC to perform the actual screenshot-taking and resizing
- It can be modified to advertise itself on the LAN via Bonjour
- HTTP handling can be improved a bit (dealing with HEAD and If-Modified-Since)
- etc., etc.
But as it is, it works just fine with my PCs and Macs (I have not tried IE, but I assume it will work), and might be just the thing for anyone wanting a very simple view-only screen sharing trick.
The choice of port 2000 is entirely arbitrary, and my guess is that a more sensible default would be 8000, 8080, 8090, etc., since those are typically open in corporate firewalls in the outbound direction (i.e., you can share your screen with somebody behind one).