diff --git a/src/Main/fg_io.cxx b/src/Main/fg_io.cxx
index b503b23..01208a4 100644
--- a/src/Main/fg_io.cxx
+++ b/src/Main/fg_io.cxx
@@ -63,6 +63,7 @@
 #include <Network/rul.hxx>
 #include <Network/generic.hxx>
 #include <Network/multiplay.hxx>
+#include <Network/screenstreamer.hxx>
 
 #include "globals.hxx"
 #include "fg_io.hxx"
@@ -198,6 +199,8 @@ FGIO::parse_port_config( const string& config )
 	    string host = tokens[3];
 	    string port = tokens[4];
 	    return new FGMultiplay(dir, atoi(rate.c_str()), host, atoi(port.c_str()));
+	} else if ( protocol == "screenstreamer" ) {
+	    return new FGScreenStreamer( tokens );
 	} else {
 	    return NULL;
 	}
diff --git a/src/Main/options.cxx b/src/Main/options.cxx
index 1f68a0a..e1de0f8 100644
--- a/src/Main/options.cxx
+++ b/src/Main/options.cxx
@@ -1370,6 +1370,7 @@ struct OptionDesc {
     {"proxy",                        true,  OPTION_FUNC,    "", false, "", fgSetupProxy },
     {"callsign",                     true, OPTION_STRING,  "sim/multiplay/callsign", false, "", 0 },
     {"multiplay",                    true,  OPTION_CHANNEL, "", false, "", 0 },
+    {"screenstreamer",               true,  OPTION_CHANNEL, "", false, "", 0 },
     {"trace-read",                   true,  OPTION_FUNC,   "", false, "", fgOptTraceRead },
     {"trace-write",                  true,  OPTION_FUNC,   "", false, "", fgOptTraceWrite },
     {"log-level",                    true,  OPTION_FUNC,   "", false, "", fgOptLogLevel },
diff --git a/src/Main/renderer.cxx b/src/Main/renderer.cxx
index 433e8da..35581f1 100644
--- a/src/Main/renderer.cxx
+++ b/src/Main/renderer.cxx
@@ -1065,6 +1065,18 @@ FGRenderer::addCamera(osg::Camera* camera, bool useSceneData)
     mRealRoot->addChild(camera);
 }
 
+void
+FGRenderer::removeCamera(osg::Camera* camera)
+{
+    mRealRoot->removeChild(camera);
+}
+
+osg::Group const *
+FGRenderer::getRoot()
+{
+    return mRoot.get();
+}
+
 bool
 fgDumpSceneGraphToFile(const char* filename)
 {
diff --git a/src/Main/renderer.hxx b/src/Main/renderer.hxx
index 3fb7e0f..945fd62 100644
--- a/src/Main/renderer.hxx
+++ b/src/Main/renderer.hxx
@@ -74,6 +74,9 @@ public:
     /** Add a top level camera.
     */
     void addCamera(osg::Camera* camera, bool useSceneData);
+    void removeCamera(osg::Camera* camera);
+
+    osg::Group const *getRoot();
 
 protected:
     osg::ref_ptr<osgViewer::Viewer> viewer;
diff --git a/src/Network/Makefile.am b/src/Network/Makefile.am
index ef53355..9cf3162 100644
--- a/src/Network/Makefile.am
+++ b/src/Network/Makefile.am
@@ -22,6 +22,7 @@ libNetwork_a_SOURCES = \
 	lfsglass.cxx lfsglass.hxx lfsglass_data.hxx \
         httpd.cxx httpd.hxx \
         $(JPEG_SERVER) \
+	screenstreamer.cxx screenstreamer.hxx \
 	joyclient.cxx joyclient.hxx \
 	jsclient.cxx jsclient.hxx \
 	native.cxx native.hxx \
diff --git a/src/Network/screenstreamer.cxx b/src/Network/screenstreamer.cxx
new file mode 100644
index 0000000..200f39e
--- /dev/null
+++ b/src/Network/screenstreamer.cxx
@@ -0,0 +1,1316 @@
+/**
+ *
+ * FG Screen Streamer
+ *
+ * Pigeon <pigeon@pigeond.net>
+ *
+ * Streaming FG screen directly over (a very basic) HTTP.
+ *
+ *
+ * Command line arguments:
+ * $ fgfs --screenstreamer=<bind address>,<port>
+ *
+ *
+ * To use the default, which will listen on localhost:20000
+ * $ fgfs --screenstreamer=, 
+ *
+ *
+ * To listen on all interfaces on port 20000:
+ * fgfs --screenstreamer=*,
+ *
+ *
+ * URL query string parameters:
+ * fps=<frame rate per second>
+ * format=rgb|mpjpeg
+ * capturer=gl|osg
+ * w=<width>
+ * h=<height>
+ * scale=1|2|4
+ * quality=<jpeg quality> (format mpjpeg only)
+ * noflip
+ *
+ * fps: frame rate per second, how many frame it should stream per second,
+ *      default is 10.
+ *
+ * format: format of the output stream, which can be:
+ *
+ * rgb    - the default if no format given. Raw rgb data.
+ * mpjpeg - multipart jpeg, useful for playback directly on a webpage, also can
+ *          be used directly in some video players. Only available if you
+ *          compile with SimGear jpeg factory support (which guarantees
+ *          libjpeg).
+ *
+ * capturer: gl or osg, osg default if available, otherwise gl.
+ *           gl capturer does not allow window resizing during a capture.
+ *           Window resizing will terminate any existing streaming.
+ *           gl capturer captures at FG window's size only.
+ *           osg capturer supports arbitrary capture/stream width and height,
+ *           and allows window resizing during streaming.
+ *
+ * w, h: arbitrary width and height, only works with the OSG capturer at the
+ *       moment. Ignored with the GL capturer.
+ *
+ * scale: the down scale factor, 1, 2 or 4, default is 1 (not scaled down).
+ *        e.g. if FG window size is 800x600, scale=2 gives 400x300 output
+ *        frames, scale=4 gives a 200x150 output frames. This is ignored with
+ *        the OSG capturer if w and h are specified.
+ *
+ * quality: the jpeg compress quality, for format mpjpeg, default is 75.
+ *
+ * noflip: do not call the flip routine, output image will be inverted, but in
+ *         theory faster for FG.
+ *
+ *
+ * URL examples:
+ * http://localhost:20000/?fps=20&format=mpjpeg&scale=2
+ * http://localhost:20000/?capturer=osg&w=320&h=240
+ * http://localhost:20000/?capturer=gl&scale=2&noflip
+ *
+ *
+ * Usage examples, assuming FG window size is 800x600.
+ *
+ * Direct playback examples:
+ *
+ * $ ffplay -f rawvideo -pix_fmt rgb24 -s 800x600 "http://localhost:20000/"
+ *
+ * $ ffplay -f rawvideo -pix_fmt rgb24 -s 400x300
+ *     "http://localhost:20000/?fps=20&scale=2"
+ *
+ * $ ffplay -f mjpeg "http://localhost:20000/?scale=2&format=mpjpeg"
+ *
+ * $ mplayer -nocache -demuxer rawvideo
+ *     -rawvideo w=800:h=600:format=rgb24:fps=20 http://localhost:20000/?fps=20
+ *
+ * $ vlc --mjpeg-fps 10 --no-drop-late-frames
+ *     "http://localhost:20000/?format=mpjpeg&fps=10"
+ *
+ * (vlc svn 0.9.0 only)
+ * $ vlc --demux rawvid --rawvid-chroma RV24
+ *     --rawvid-width 800 --rawvid-height 600
+ *     --no-drop-late-frames "http://localhost:20000/"
+ *
+ *
+ * Save and playback later (file extension is important for ffplay and mplayer)
+ *
+ * $ wget -O fg.mjpg "http://localhost:20000/?format=mpjpeg&fps=20"
+ * $ ffplay fg.mjpg
+ * $ mplayer -fps 20 fg.mjpg
+ * $ vlc --mjpeg-fps=20 fg.mjpg
+ *
+ *
+ * Save and play at the same time:
+ * $ wget -O - "http://localhost:20000/?fps=20&scale=2&format=mpjpeg" |
+ *     tee fg.mjpg | ffplay -f mjpeg -
+ *
+ *
+ * Direct video encoding:
+ * $ ffmpeg -r 20 -f rawvideo -pix_fmt rgb24 -s 400x300 -i
+ *     "http://localhost:20000/?fps=20&scale=2" -vcodec mpeg4
+ *     -b 256 -an -vtag DIVX output.avi
+ *
+ * $ mencoder -nocache -demuxer rawvideo
+ *     -rawvideo w=640:h=480:fps=25:format=rgb24
+ *     -ovc lavc -lavcopts vcodec=mpeg4:vbitrate=480
+ *     -o output.avi -ffourcc DIVX
+ *     "http://localhost:20000/?fps=25&capturer=osg&w=640&h=480"
+ *
+ *
+ * Playing in a webpage directly (only works in Mozilla/Firefox/etc):
+ * Put the following tag in your HTML page
+ * <img src="http://localhost:20000/?format=mpjpeg">
+ * And you may want to change localhost to something else.
+ *
+ *
+ * Watching, while encoding and streaming over HTTP, with VLC:
+ *
+ * vlc "http://localhost:20000/?fps=10&scale=2"
+ *   --no-sout-transcode-hurry-up --no-sout-ffmpeg-hurry-up
+ *   --sout-transcode-fps 10 --no-drop-late-frames
+ *   --demux rawvid --rawvid-width 400 --rawvid-height 300 --rawvid-fps 10
+ *   --rawvid-chroma RV24 --sout
+ *   "#duplicate{dst=display,dst='transcode{vcodec=mp4v,vb=256}:
+ *   standard{access=http,mux=asf,dst=0.0.0.0:8080}'}"
+ *
+ *
+ * Notes:
+ *
+ * - Under Linux at least, ffplay/ffmpeg and mplayer/mencoder (-nocache is
+ *   important!) seems to work pretty well. vlc also works, but seems to
+ *   complain more about late frames every now and then.
+ *
+ * - An output width and height of multiple of 2 is recommended. Hence if
+ *   you're using scale=2, you should make sure FG's window width and height
+ *   are multiple of 4. And for scale=4, multiple of 8.
+ *
+ * - If you seriously want to capture audio as well, you'll have to record it
+ *   from the audio device. Under Linux you can do so with ffmpeg by doing:
+ *   $ ffmpeg -r 20 -f rawvideo -pix_fmt rgb24 -s 400x300 -i
+ *       "http://localhost:20000/?fps=20&scale=2" -vcodec mpeg4
+ *       -f audio_device -i /dev/dsp -ab 128k -acodec mp3 output.avi
+ *
+ *   Note that with this method the video and audio is almost definitely to be
+ *   a bit out of sync.
+ *
+ *
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <simgear/compiler.h>
+#include <simgear/misc/strutils.hxx>
+#include <simgear/debug/logstream.hxx>
+
+#include <plib/netChannel.h>
+#include <plib/netChat.h>
+
+#include <iostream>
+
+#include <Main/renderer.hxx>
+#include <Main/globals.hxx>
+#include <Main/fg_props.hxx>
+
+#include "screenstreamer.hxx"
+ 
+#if defined(__linux__)
+#include <sys/socket.h>
+static const int sendFlags = MSG_NOSIGNAL;
+#else
+static const int sendFlags = 0;
+#endif
+
+#define SCREENSTREAM_TERMINATOR "\r\n"
+
+
+enum {
+    SCREENSTREAM_DEFAULT_PORT = 20000,
+    SCREENSTREAM_DEFAULT_FPS = 10,
+    SCREENSTREAM_MAX_FPS = 50,
+
+    SCREENSTREAM_MPJPEG_DEFAULT_QUALITY = 75,
+};
+
+
+/* Helpers */
+static int
+netChannelSend(netChannel *channel, const void *buffer, int size) {
+
+    if ((channel != NULL) &&
+            (channel->isConnected()) &&
+            (!channel->isClosed()) &&
+            (channel->getHandle() != -1)) {
+
+        return channel->send(buffer, size, sendFlags);
+    }
+
+    return -1;
+}
+
+
+/* Base screen capturer class */
+class ScreenCapturer {
+
+public:
+    ScreenCapturer(unsigned int w, unsigned int h, unsigned int s, bool f);
+    virtual ~ScreenCapturer() {};
+
+    virtual bool finished() { return finish; };
+
+    virtual bool capture() = 0;
+    virtual const unsigned char *getData() = 0;
+    virtual void getDimension(unsigned int &width, unsigned int &height) = 0;
+
+    unsigned int getBpp() { return bpp; };
+
+
+protected:
+    unsigned int bpp; /* byte per pixel */
+    unsigned int scale;
+    bool flip;
+    bool finish;
+};
+
+
+ScreenCapturer::ScreenCapturer(unsigned int w, unsigned int h,
+        unsigned int s, bool f):
+    bpp(0),
+    scale(s),
+    flip(f),
+    finish(false) {
+}
+
+
+
+/* Screen capturer class using glReadPixels */
+class GLScreenCapturer: public ScreenCapturer, public SGPropertyChangeListener {
+
+public:
+    GLScreenCapturer(unsigned int w, unsigned int h, unsigned int s, bool f);
+    virtual ~GLScreenCapturer();
+
+    bool capture();
+    const unsigned char *getData();
+    void getDimension(unsigned int &width, unsigned int &height);
+
+    /* Halt on a screen size change */
+    void valueChanged(SGPropertyNode *node) { finish = true; };
+
+
+private:
+    unsigned int captureWidth;
+    unsigned int captureHeight;
+    unsigned char *captureBuf;
+    unsigned int captureBufSize;
+
+    /* if we are doing our own simple software scaling */
+    unsigned char *scaledBuf;
+    unsigned int scaledBufSize;
+
+    unsigned int scaledWidth;
+    unsigned int scaledHeight;
+
+    SGPropertyNode_ptr widthNode;
+    SGPropertyNode_ptr heightNode;
+
+    void flipVertical();
+};
+
+
+GLScreenCapturer::GLScreenCapturer(unsigned int w, unsigned int h,
+        unsigned int s, bool f):
+    ScreenCapturer(w, h, s, f) {
+
+    /* GLScreenCapturer does not support arbitrary width and height, we ignore
+     * w and h */
+
+    captureWidth = fgGetInt("/sim/startup/xsize");
+    captureHeight = fgGetInt("/sim/startup/ysize");
+
+    /* Always ask GL to give us RGB24, 3 bytes per pixel */
+    bpp = 3;
+
+    captureBufSize = captureWidth * captureHeight * bpp;
+    captureBuf = new unsigned char[captureBufSize];
+
+    scaledBuf = 0;
+
+    if (scale != 1) {
+        /* We are scaling */
+        scaledWidth = captureWidth / scale;
+        scaledHeight = captureHeight / scale;
+        scaledBufSize = scaledWidth * scaledHeight * bpp;
+        scaledBuf = new unsigned char[scaledBufSize];
+    }
+
+    /* Listen for screen change */
+    widthNode = fgGetNode("/sim/startup/xsize", false);
+    heightNode = fgGetNode("/sim/startup/ysize", false);
+    widthNode->addChangeListener(this);
+    heightNode->addChangeListener(this);
+
+}
+
+
+GLScreenCapturer::~GLScreenCapturer() {
+    widthNode->removeChangeListener(this);
+    heightNode->removeChangeListener(this);
+    delete[] captureBuf;
+    delete[] scaledBuf;
+}
+
+
+const unsigned char *
+GLScreenCapturer::getData() {
+    if (scale == 1) {
+        return captureBuf;
+    } else {
+        return scaledBuf;
+    }
+}
+
+
+void
+GLScreenCapturer::getDimension(unsigned int &width, unsigned int &height) {
+    if (scale == 1) {
+        width = captureWidth;
+        height = captureHeight;
+    } else {
+        width = scaledWidth;
+        height = scaledHeight;
+    }
+}
+
+
+bool
+GLScreenCapturer::capture() {
+
+    GLint unpack, pack;
+
+    /* Save the current GL packing and alignment */
+    glGetIntegerv(GL_UNPACK_ALIGNMENT, &unpack);
+    glGetIntegerv(GL_PACK_ALIGNMENT, &pack);
+
+    /* Get GL to pack tight */
+    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
+    glPixelStorei(GL_PACK_ALIGNMENT, 1);
+
+    //globals->get_renderer()->update();
+
+    glReadPixels(0, 0, captureWidth, captureHeight, GL_RGB,
+            GL_UNSIGNED_BYTE, captureBuf);
+
+    /* Restore the old GL packing and alignment */
+    glPixelStorei(GL_UNPACK_ALIGNMENT, unpack);
+    glPixelStorei(GL_PACK_ALIGNMENT, pack);
+
+
+    /* Do our simple scaling */
+    if (scale != 1) {
+        unsigned char *p = (unsigned char *) captureBuf;
+        unsigned char *sp = scaledBuf;
+
+        int m = scaledHeight;
+
+        while (m--) {
+
+            int n = scaledWidth;
+
+            while (n--) {
+                *sp++ = *p++;
+                *sp++ = *p++;
+                *sp++ = *p++;
+                p += (scale - 1) * bpp;
+            }
+
+            p += (scale - 1) * bpp * captureWidth;
+        }
+    }
+
+    if (flip) {
+        flipVertical();
+    }
+
+    return true;
+}
+
+
+void
+GLScreenCapturer::flipVertical() {
+
+    unsigned int m, n, h;
+    unsigned char *buf, *pTop, *pBot;
+
+    if (scale == 1) {
+        m = captureHeight / 2;
+        n = captureWidth * bpp;
+        h = captureHeight;
+        buf = pTop = captureBuf;
+    } else {
+        m = scaledHeight / 2;
+        n = scaledWidth * bpp;
+        h = scaledHeight;
+        buf = pTop = scaledBuf;
+    }
+
+    unsigned char tmp;
+
+    /* Reverse rows by swapping top and bottom rows repeatedly till we reach
+     * the middle row */
+
+    for (unsigned int y = 0; y < m; y++) {
+
+        pBot = buf + (h - y - 1) * n;
+
+        int x = n;
+
+        while (x--) {
+            tmp = *pTop;
+            *pTop = *pBot;
+            *pBot = tmp;
+            pTop++;
+            pBot++;
+        }
+    }
+}
+
+
+
+#define HAVE_OSG 1
+
+#if HAVE_OSG
+
+#include <osgUtil/SceneView>
+extern osg::ref_ptr<osgUtil::SceneView> sceneView;
+
+/* OSG capturer: using OSG render to buffer */
+class OSGScreenCapturer: public ScreenCapturer {
+
+public:
+    OSGScreenCapturer(unsigned int w, unsigned int h, unsigned int s, bool f);
+    ~OSGScreenCapturer();
+
+    bool capture();
+    const unsigned char *getData();
+    void getDimension(unsigned int &width, unsigned int &height);
+
+
+private:
+    osg::ref_ptr<osg::Camera> camera; /* our rtt camera */
+
+    unsigned int imageWidth;
+    unsigned int imageHeight;
+    osg::ref_ptr<osg::Image> image;
+
+    osg::Camera *mainCamera;
+};
+
+
+OSGScreenCapturer::OSGScreenCapturer(unsigned int w, unsigned int h,
+        unsigned int s, bool f):
+    ScreenCapturer(w, h, s, f) {
+
+    /* Ignore scale if we are using arbitrary width/height */
+    if (w == 0 || h == 0) {
+        imageWidth = fgGetInt("/sim/startup/xsize");
+        imageHeight = fgGetInt("/sim/startup/ysize");
+        imageWidth /= s;
+        imageHeight /= s;
+    } else {
+        imageWidth = w;
+        imageHeight = h;
+    }
+
+    /* We always want RGB24, 3 bytes per pixel*/
+    bpp = 3;
+
+    image = new osg::Image;
+    image->allocateImage(imageWidth, imageHeight, 1, GL_RGB, GL_UNSIGNED_BYTE);
+
+    camera = new osg::Camera;
+    camera->setClearColor(osg::Vec4(0., 0., 0., 1.));
+    camera->setClearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+    camera->setComputeNearFarMode(osg::CullSettings::DO_NOT_COMPUTE_NEAR_FAR);
+    camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
+    camera->setViewport(0, 0, imageWidth, imageHeight);
+
+    camera->setRenderOrder(osg::Camera::PRE_RENDER);
+    camera->setRenderTargetImplementation(osg::Camera::FRAME_BUFFER_OBJECT,
+            osg::Camera::FRAME_BUFFER);
+
+    camera->attach(osg::Camera::COLOR_BUFFER, image.get());
+    camera->setNodeMask(0);
+
+    FGRenderer *renderer = globals->get_renderer();
+    osgViewer::Viewer *viewer = renderer->getViewer();
+
+    if (viewer) {
+        mainCamera = viewer->getCamera();
+        renderer->addCamera(camera.get(), false);
+        camera->addChild((osg::Node *) renderer->getRoot());
+    } else {
+        mainCamera = sceneView->getCamera();
+        camera->addChild(sceneView->getSceneData());
+    }
+}
+
+
+OSGScreenCapturer::~OSGScreenCapturer() {
+    osgViewer::Viewer *viewer = globals->get_renderer()->getViewer();
+    if (viewer) {
+        globals->get_renderer()->removeCamera(camera.get());
+    }
+}
+
+
+void
+OSGScreenCapturer::getDimension(unsigned int &width, unsigned int &height) {
+    width = imageWidth;
+    height = imageHeight;
+}
+
+
+const unsigned char *
+OSGScreenCapturer::getData() {
+    return image->data();
+}
+
+
+bool
+OSGScreenCapturer::capture() {
+
+    osgViewer::Viewer *viewer = globals->get_renderer()->getViewer();
+
+    camera->setNodeMask(1);
+
+    if (viewer) {
+        camera->setViewMatrix(mainCamera->getViewMatrix());
+        camera->setProjectionMatrix(mainCamera->getProjectionMatrix());
+        viewer->frame();
+    } else {
+        camera->setViewMatrix(mainCamera->getViewMatrix());
+        camera->setProjectionMatrix(mainCamera->getProjectionMatrix());
+        osg::Node *old = sceneView->getSceneData();
+        sceneView->setSceneData(camera.get());
+        sceneView->update();
+        sceneView->cull();
+        sceneView->draw();
+        sceneView->setSceneData(old);
+    }
+
+    camera->setNodeMask(0);
+
+    if (flip) {
+        image->flipVertical();
+    }
+
+    return true;
+}
+#endif
+
+
+
+/* ScreenStreamer base class */
+class ScreenStreamer {
+
+public:
+    ScreenStreamer(netChannel *c, string method,
+            unsigned int width, unsigned int height,
+            unsigned int scale, bool flip);
+    virtual ~ScreenStreamer();
+
+    virtual void setQuality(unsigned int q) {};
+
+    bool stream();
+    void shouldFinish() { finish = true; };
+    void getDimension(unsigned int &width, unsigned int &height);
+    int getBpp();
+
+    /* Returns any extra HTTP headers */
+    virtual const char *getHeader() { return 0; };
+
+
+protected:
+    ScreenCapturer *capturer;
+
+    /* The width and height the capturer giving us */
+    unsigned int captureWidth, captureHeight;
+    unsigned int captureBpp;
+
+    /* Process the data from the capturer */
+    virtual bool process() { return true; };
+
+    /* Returns the pointer to the * current frame */
+    virtual const unsigned char *getFrame() = 0;
+
+    /* Returns the size of the current frame */
+    virtual unsigned int getFrameSize() = 0;
+
+
+private:
+    netChannel *channel;
+    bool isChannelReady();
+
+    bool finish;
+
+    unsigned int frameSent;
+    unsigned int frameLeft;
+
+    bool streaming;
+};
+
+
+
+ScreenStreamer::ScreenStreamer(netChannel *c, string method,
+        unsigned int width, unsigned int height, unsigned int scale, bool flip):
+
+    channel(c),
+    finish(false),
+    frameSent(0),
+    frameLeft(0),
+    streaming(false) {
+
+    if (method == "gl") {
+        capturer = new GLScreenCapturer(width, height, scale, flip);
+#if HAVE_OSG
+    } else if (method == "osg") {
+        capturer = new OSGScreenCapturer(width, height, scale, flip);
+#endif
+    }
+
+    capturer->getDimension(captureWidth, captureHeight);
+    captureBpp = capturer->getBpp();
+
+}
+
+
+ScreenStreamer::~ScreenStreamer() {
+    delete capturer;
+}
+
+
+bool
+ScreenStreamer::stream() {
+
+    const unsigned char *sendData = NULL;
+    int sent;
+
+    if (streaming) {
+        return true;
+    }
+
+    streaming = true;
+    
+    if (frameLeft == 0) {
+
+        /* Fresh frame, capture new frame and process */
+        if (capturer->capture() == false ||
+                process() == false) {
+            return false;
+        }
+
+        sendData = getFrame();
+        frameLeft = getFrameSize();
+        frameSent = 0;
+
+    } else {
+
+        /* Continue to send what's left */
+        sendData = getFrame() + frameSent;
+    }
+
+    if (finish) {
+        return false;
+    }
+
+    sent = netChannelSend(channel, sendData, frameLeft);
+
+    if (sent < 0) {
+        return false;
+    }
+
+    frameSent += sent;
+    frameLeft -= sent;
+
+    if (frameLeft > 0) {
+        /* Add a short timer to retry */
+        globals->get_event_mgr()->addEvent("ScreenStreamer",
+                this, &ScreenStreamer::stream, 0.02);
+    }
+
+    streaming = false;
+
+    return true;
+}
+
+
+void
+ScreenStreamer::getDimension(unsigned int &width, unsigned int &height) {
+    width = captureWidth;
+    height = captureHeight;
+}
+
+
+int
+ScreenStreamer::getBpp() {
+    return captureBpp;
+}
+
+
+bool
+ScreenStreamer::isChannelReady() {
+    return ((channel != NULL) &&
+            (channel->isConnected()) &&
+            (!channel->isClosed()) &&
+            (channel->getHandle() != -1));
+}
+
+
+
+/* RgbStreamer, which streams raw RGB frames */
+class RgbStreamer: public ScreenStreamer {
+
+public:
+    RgbStreamer(netChannel *c, string method,
+            unsigned int width, unsigned int height,
+            unsigned int scale, bool flip);
+    virtual ~RgbStreamer();
+
+
+protected:
+    const unsigned char *getFrame();
+    unsigned int getFrameSize();
+
+
+private:
+    const unsigned char *frame;
+    unsigned int frameSize;
+};
+
+
+RgbStreamer::RgbStreamer(netChannel *c, string method,
+            unsigned int width, unsigned int height,
+            unsigned int scale, bool flip):
+    ScreenStreamer(c, method, width, height, scale, flip) {
+
+    frame = capturer->getData();
+    frameSize = captureWidth * captureHeight * captureBpp;
+}
+
+
+RgbStreamer::~RgbStreamer() {
+
+}
+
+
+const unsigned char *
+RgbStreamer::getFrame() {
+    return frame;
+}
+
+
+unsigned int
+RgbStreamer::getFrameSize() {
+    return frameSize;
+}
+
+
+
+#ifdef HAVE_LIBJPEG
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <jpeglib.h>
+
+/* libjpeg destination manager callback functions */
+static void init_destination(j_compress_ptr cinfo);
+static boolean empty_output_buffer(j_compress_ptr cinfo);
+static void term_destination(j_compress_ptr cinfo);
+
+#ifdef __cplusplus
+}
+#endif
+
+#define MPJPEG_BOUNDARY     "FlightGearMpJpegBoundary"
+#define MPJPEG_MPTYPE       "multipart/x-mixed-replace;boundary="
+#define MPJPEG_JPEGTYPE     "image/jpeg"
+
+class MpJpegStreamer;
+
+/* libjpeg destination manager stuff */
+typedef struct {
+
+    struct jpeg_destination_mgr pub;
+
+    JOCTET *buf;                /* full buffer to send */
+    unsigned int bufsize;       /* full buffer size */
+
+    JOCTET *workbuf;            /* working buffer for libjpeg */
+    unsigned int workbufsize;   /* working buffer size */
+
+    MpJpegStreamer *streamer;
+
+} my_destination_mgr;
+
+typedef my_destination_mgr *my_dest_ptr;
+
+
+/* MpJpegStreamer: multipart jpeg streamer */
+class MpJpegStreamer: public ScreenStreamer {
+
+public:
+    MpJpegStreamer(netChannel *c, string method,
+            unsigned int width, unsigned int height,
+            unsigned int scale, bool flip);
+    virtual ~MpJpegStreamer();
+
+    void setQuality(unsigned int q);
+
+    const char *getHeader();
+
+
+protected:
+    bool process();
+    const unsigned char *getFrame();
+    unsigned int getFrameSize();
+
+
+private:
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+    my_dest_ptr dest;
+    JSAMPROW *row_pointers;
+
+    unsigned int frameSize;
+};
+
+
+MpJpegStreamer::MpJpegStreamer(netChannel *c, string method,
+        unsigned int width, unsigned int height, unsigned int scale, bool flip):
+
+    /* We don't need to ask the streamer to do flipping */
+    ScreenStreamer(c, method, width, height, scale, false) {
+
+    /* jpeg compression setup */
+    cinfo.err = jpeg_std_error(&jerr);
+    jpeg_create_compress(&cinfo);
+
+    /* jpeg compression destination manager */
+    cinfo.dest = (struct jpeg_destination_mgr *)
+        (cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT,
+                sizeof(my_destination_mgr));
+
+    dest = (my_dest_ptr) cinfo.dest;
+
+    /* Buffer large enough for the entire jpeg */
+    dest->bufsize = captureWidth * captureHeight * 3 * sizeof(JOCTET);
+
+    dest->buf = (JOCTET *) (cinfo.mem->alloc_large)
+        ((j_common_ptr) &cinfo, JPOOL_PERMANENT, dest->bufsize);
+
+    dest->pub.init_destination = init_destination;
+    dest->pub.empty_output_buffer = empty_output_buffer;
+    dest->pub.term_destination = term_destination;
+    dest->streamer = this;
+
+    /* compression parameters */
+    cinfo.image_width = captureWidth;
+    cinfo.image_height = captureHeight;
+    cinfo.input_components = 3;
+    cinfo.in_color_space = JCS_RGB;
+
+    jpeg_set_defaults(&cinfo);
+
+    /* Setup our scanlines row pointers for libjpeg */
+    row_pointers = new JSAMPROW[captureHeight];
+    int y = captureHeight;
+    const unsigned char *captureData = capturer->getData();
+
+    if (flip) {
+        while (y--) {
+            row_pointers[y] = (JSAMPROW)
+                &(captureData[(captureHeight - y - 1) * captureWidth * 3]);
+        }
+    } else {
+        while (y--) {
+            row_pointers[y] = (JSAMPROW)
+                &(captureData[y * captureWidth * 3]);
+        }
+    }
+
+    /* Header and footer for each jpeg frame */
+    static const char frameHeader[] =
+        "--" MPJPEG_BOUNDARY SCREENSTREAM_TERMINATOR \
+        "Content-type: " MPJPEG_JPEGTYPE \
+        SCREENSTREAM_TERMINATOR SCREENSTREAM_TERMINATOR;
+
+    int len = strlen(frameHeader);
+    strcpy((char *) dest->buf, frameHeader);
+    dest->workbuf = dest->buf + len;
+    dest->workbufsize = dest->bufsize - len;
+}
+
+
+MpJpegStreamer::~MpJpegStreamer() {
+    delete[] row_pointers;
+    jpeg_abort_compress(&cinfo);
+    jpeg_destroy_compress(&cinfo);
+};
+
+
+void
+MpJpegStreamer::setQuality(unsigned int q) {
+    jpeg_set_quality(&cinfo, q, TRUE);
+}
+
+
+const char *
+MpJpegStreamer::getHeader() {
+    return "Content-type: " MPJPEG_MPTYPE \
+        MPJPEG_BOUNDARY SCREENSTREAM_TERMINATOR;
+}
+
+
+bool
+MpJpegStreamer::process() {
+
+    jpeg_start_compress(&cinfo, TRUE);
+
+    if (jpeg_write_scanlines(&cinfo, row_pointers, captureHeight)
+            != captureHeight) {
+        return false;
+    }
+
+    jpeg_finish_compress(&cinfo);
+
+    /* Append frame footer after the jpeg */
+    static const char frameFooter[] = SCREENSTREAM_TERMINATOR;
+    int len = strlen(frameFooter);
+
+    unsigned int jpegSize = dest->workbufsize - dest->pub.free_in_buffer;
+
+    memcpy(dest->workbuf + jpegSize, frameFooter, len);
+
+    frameSize = (dest->workbuf - dest->buf) + jpegSize + len;
+
+    return true;
+}
+
+
+const unsigned char *
+MpJpegStreamer::getFrame() {
+    return ((my_dest_ptr) cinfo.dest)->buf;
+}
+
+
+unsigned int
+MpJpegStreamer::getFrameSize() {
+    return frameSize;
+}
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+static void
+init_destination(j_compress_ptr cinfoPtr) {
+    my_dest_ptr dest = (my_dest_ptr) cinfoPtr->dest;
+    dest->pub.next_output_byte = dest->workbuf;
+    dest->pub.free_in_buffer = dest->workbufsize;
+}
+
+
+static boolean
+empty_output_buffer(j_compress_ptr cinfoPtr) {
+    my_dest_ptr dest = (my_dest_ptr) cinfoPtr->dest;
+    dest->pub.next_output_byte = dest->workbuf;
+    dest->pub.free_in_buffer = dest->workbufsize;
+    dest->streamer->shouldFinish();
+    return TRUE;
+}
+
+
+static void
+term_destination(j_compress_ptr cinfoPtr) {
+    /* Do nothing */
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* HAVE_LIBJPEG */
+
+
+
+
+class ScreenStreamChannel: public netChat {
+
+public:
+    ScreenStreamChannel();
+    ~ScreenStreamChannel();
+
+    /* netChat methods */
+    virtual void collectIncomingData(const char *s, int n);
+    virtual void foundTerminator();
+
+
+private:
+    void stream();
+
+    netBuffer buffer;
+
+    int fps;
+    double delay;
+
+    ScreenStreamer *streamer;
+};
+
+
+ScreenStreamChannel::ScreenStreamChannel():
+    buffer(512),
+    fps(0),
+    streamer(NULL) {
+
+    setTerminator(SCREENSTREAM_TERMINATOR);
+}
+
+
+ScreenStreamChannel::~ScreenStreamChannel() {
+    buffer.remove();
+    delete streamer;
+}
+
+
+void ScreenStreamChannel::collectIncomingData(const char *s, int n) {
+    buffer.append(s, n);
+}
+
+
+void
+ScreenStreamChannel::foundTerminator() {
+
+    if (streamer != NULL) {
+        return;
+    }
+
+    vector<string> tokens = simgear::strutils::split(buffer.getData());
+
+    if (tokens.empty()) {
+        return;
+    }
+
+    string cmd = tokens[0];
+    string url = tokens[1];
+
+    if (cmd != "GET") {
+        shouldDelete();
+        return;
+    }
+
+    unsigned int scale = 0;
+    unsigned int quality = 0;
+    unsigned int w = 0, h = 0;
+    bool flip = true;
+    string format;
+    string method;
+
+    string::size_type i = url.find("?");
+
+    if (i != string::npos) {
+
+        /* We don't care about the file name at the moment */
+        url.erase(0, i + 1);
+
+        /* Query string parsing */
+        vector<string> params = simgear::strutils::split(url, "&");
+
+        vector<string>::iterator iter;
+
+        for (iter = params.begin(); iter != params.end(); iter++) {
+
+            vector<string> pair = simgear::strutils::split(*iter, "=");
+
+            if (pair.size() == 0) {
+                continue;
+            }
+
+            /* Params:
+             * fps=<frame rate per second>
+             * format=rgb|mpjpeg
+             * noflip
+             * capturer=gl|osg
+             * scale=1|2|4
+             * quality=75 (mpjpeg only)
+             * w=<width> (if capturer support arbitrary width)
+             * h=<height> (if capturer supported arbitrary height)
+             */
+            if (pair.size() == 2) {
+                if (pair[0] == "fps") {
+                    fps = atoi(pair[1].c_str());
+                } else if (pair[0] == "format") {
+                    format = pair[1];
+                } else if (pair[0] == "scale") {
+                    scale = atoi(pair[1].c_str());
+                } else if (pair[0] == "quality") {
+                    quality = atoi(pair[1].c_str());
+                } else if (pair[0] == "capturer") {
+                    method = pair[1];
+                } else if (pair[0] == "w") {
+                    w = atoi(pair[1].c_str());
+                } else if (pair[0] == "h") {
+                    h = atoi(pair[1].c_str());
+                } else if (pair[0] == "noflip") {
+                    flip = false;
+                }
+            }
+        }
+    }
+
+    if (scale != 1 && scale != 2 && scale != 4) {
+        scale = 1;
+    }
+
+    if (method != "gl" && method != "osg") {
+#if HAVE_OSG
+        method = "osg";
+#else
+        method = "gl";
+#endif
+    }
+
+
+    /* Set our socket to non blocking for the streamer */
+    setBlocking(false);
+
+
+    if (format == "rgb") {
+        streamer = new RgbStreamer(this, method, w, h, scale, flip);
+#ifdef HAVE_LIBJPEG
+    } else if (format == "mpjpeg") {
+        streamer = new MpJpegStreamer(this, method, w, h, scale, flip);
+#endif
+    } else {
+        streamer = new RgbStreamer(this, method, w, h, scale, flip);
+    }
+
+    if (quality == 0 || quality > 100) {
+        quality = SCREENSTREAM_MPJPEG_DEFAULT_QUALITY;
+    }
+    streamer->setQuality(quality);
+
+    char resp[BUFSIZ];
+
+    /* Always OK, for now... */
+    strcpy(resp, "HTTP/1.1 200 OK");
+    strcat(resp, getTerminator());
+
+    if (netChannelSend(this, resp, strlen(resp)) <= 0) {
+        shouldDelete();
+        return;
+    }
+
+    /* Our extra information headers */
+    streamer->getDimension(w, h);
+    snprintf(resp, sizeof(resp),
+            "X-Frame-Dimension: %dx%d%s", w, h, getTerminator());
+
+    if (netChannelSend(this, resp, strlen(resp)) <= 0) {
+        shouldDelete();
+        return;
+    }
+
+    snprintf(resp, sizeof(resp),
+            "X-Frame-BytePerPixel: %d%s", streamer->getBpp(), getTerminator());
+
+    if (netChannelSend(this, resp, strlen(resp)) <= 0) {
+        shouldDelete();
+        return;
+    }
+
+
+    const char *header = streamer->getHeader();
+    if (header) {
+        if (netChannelSend(this, header, strlen(header)) <= 0) {
+            shouldDelete();
+            return;
+        }
+    }
+
+    strcpy(resp, getTerminator());
+
+    if (netChannelSend(this, (char *) resp, strlen(resp)) <= 0) {
+        shouldDelete();
+        return;
+    }
+
+
+    if (fps <= 0 || fps > SCREENSTREAM_MAX_FPS) {
+        fps = SCREENSTREAM_DEFAULT_FPS;
+    }
+
+    delay = 1.0 / fps;
+
+#if 1
+    globals->get_event_mgr()->addEvent("ScreenStream",
+            this, &ScreenStreamChannel::stream, delay);
+#else
+    /* There is currently no way to remove a task from addTask, so I'm not
+     * using it for now */
+    globals->get_event_mgr()->addTask("ScreenStream",
+            this, &ScreenStreamChannel::stream, delay);
+#endif
+
+}
+
+
+void
+ScreenStreamChannel::stream() {
+
+    if (isClosed() || !isConnected() || getHandle() == -1) {
+        shouldDelete();
+        return;
+    }
+
+    if (streamer->stream() == false) {
+        shouldDelete();
+        return;
+    }
+
+    globals->get_event_mgr()->addEvent("ScreenStream",
+            this, &ScreenStreamChannel::stream, delay);
+}
+
+
+
+FGScreenStreamer::FGScreenStreamer(const vector<string>& tokens) {
+
+    port = SCREENSTREAM_DEFAULT_PORT;
+
+    if (tokens.size() >= 2) {
+        bindAddr = tokens[1];
+    }
+
+    if (tokens.size() == 3) {
+        port = atoi(tokens[2].c_str());
+    }
+
+    if (port <= 0 || port >= 65536) {
+        port = SCREENSTREAM_DEFAULT_PORT;
+    }
+
+    set_hz(5);
+}
+
+
+FGScreenStreamer::~FGScreenStreamer() {
+}
+
+
+bool
+FGScreenStreamer::open() {
+
+    if (is_enabled()) {
+        return false;
+    }
+
+    netChannel::open();
+
+    /* TODO: plib's netSocket bind seems to bind to another port if the one we
+     * requested can't be bound, but there is no way to find out what it is */
+    netChannel::bind((bindAddr.empty() ? "127.0.0.1" : bindAddr.c_str()), port);
+
+    netChannel::listen(5);
+
+    set_enabled(true);
+
+    return true;
+}
+
+
+bool
+FGScreenStreamer::process() {
+    netChannel::poll();
+    return true;
+}
+
+
+bool
+FGScreenStreamer::close() {
+    return true;
+}
+
+
+void
+FGScreenStreamer::handleAccept () {
+    netAddress addr;
+    int handle = accept(&addr);
+    ScreenStreamChannel *channel = new ScreenStreamChannel();
+    channel->setHandle(handle);
+}
+
diff --git a/src/Network/screenstreamer.hxx b/src/Network/screenstreamer.hxx
new file mode 100644
index 0000000..1f3a288
--- /dev/null
+++ b/src/Network/screenstreamer.hxx
@@ -0,0 +1,35 @@
+#ifndef _FG_SCREENSTREAMER_HXX
+#define _FG_SCREENSTREAMER_HXX
+
+#include <simgear/compiler.h>
+
+#include STL_STRING
+#include <vector>
+SG_USING_STD(string);
+SG_USING_STD(vector);
+
+#include <plib/netChannel.h>
+
+#include "protocol.hxx"
+
+
+class FGScreenStreamer : public FGProtocol, public netChannel {
+
+public:
+    FGScreenStreamer(const vector<string>& tokens);
+    virtual ~FGScreenStreamer();
+
+    void handleAccept();
+
+    bool open();
+    bool process();
+    bool close();
+
+
+private:
+    string bindAddr;
+    int port;
+};
+
+
+#endif

