This implements the following repairs to the "vnc" plugin, without
intending to add any new features:
*  Explicit locking of the image reference between the OSG and RFB
rendering threads
*  Keep the RFB protocol fully active until we see a callback to
setFrameLastRendered()
*  Allow the RFB thread to become active once we're starting to
deallocate the image
*  Fill in the top byte as 255 in the image buffer, so the content is
usable as RGBA
*  Ignore any directory path in the passed filename, just use the
basename portion
*  As with a conventional file-loaded image, set the filename property
of the image
*  Wait until the RFB system knows our image size before returning its reference

The first three changes are needed for the plugin to work from osgvnc
and osgviewer on my machine.  The remaining four allow the plugin to
be implicitly referenced from the OSG .ac model reader for building
scene graphs within SimGear.
/* -*-c++-*- OpenSceneGraph - Copyright (C) 1999-2008 Robert Osfield 
 *
 * This software is open source and may be redistributed and/or modified under  
 * the terms of the GNU General Public License (GPL) version 2.0.
 * The full license is in LICENSE.txt file included with this distribution,.
 * 
 * This software 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 
 * include LICENSE.txt for more details.
*/

#include <osg/Timer>

#include <osgDB/ReaderWriter>
#include <osgDB/FileNameUtils>
#include <osgDB/Registry>

#include <osgWidget/VncClient>

extern "C" {
#include <rfb/rfbclient.h>
}

class LibVncImage : public osgWidget::VncImage
{
    public:
    
        LibVncImage();

        bool connect(const std::string& hostname);

        void close();
        
        virtual bool sendPointerEvent(int x, int y, int buttonMask);

        double getTimeOfLastUpdate() const { return _timeOfLastUpdate; }
        double getTimeOfLastRender() const { return _timeOfLastRender; }

        double time() const { return osg::Timer::instance()->time_s(); }

        virtual bool sendKeyEvent(int key, bool keyDown);

        // If the image is used outside the rendering pipeline,
        // this will never be called.  So we defer relying on it.
        virtual void setFrameLastRendered(const osg::FrameStamp* frameStamp);

        void updated();

        static rfbBool resizeImage(rfbClient* client);
        
        static void updateImage(rfbClient* client,int x,int y,int w,int h);
        
        double                      _timeOfLastUpdate;
        double                      _timeOfLastRender;

        bool                        _active;
        osg::ref_ptr<osg::RefBlock> _inactiveBlock;

    protected:
    
        virtual ~LibVncImage();

        class RfbThread : public osg::Referenced, public OpenThreads::Thread
        {
        public:

            RfbThread(rfbClient* client, LibVncImage* image):
                _client(client),
                _image(image),
                _done(false) {}

            virtual ~RfbThread()
            {
                _done = true;
                while(isRunning()) 
                {
                    OpenThreads::Thread::YieldCurrentThread();
                }
            }

            virtual void run()
            {
                do
                {
                    // Refresh our reference every time through, so it can die
                    osg::ref_ptr<LibVncImage> image = _image.lock();

                    if (image->_active)
                    {               
                        int i=WaitForMessage(_client,5000);
                        if(i<0)
                            return;

                        if(i)
                        {
                            // osg::notify(osg::NOTICE)<<"Handling "<<i<<" messages"<<std::endl;
                        
                            if(!HandleRFBServerMessage(_client))
                            return;

                            image->updated();
                        }
                    }
                    else
                    {
                        image->_inactiveBlock->block();
                    }
                    

                    // Make sure we are getting frame rendering times    
                    double rendered = image->getTimeOfLastRender();
                    if ((rendered != 0) && (rendered < image->getTimeOfLastUpdate() - 0.01))
                    {
                        osg::notify(osg::NOTICE)<<"Going inactive"<<std::endl;
                        image->_active = false;
                    }
                } while (!_done && !testCancel());
            }

            rfbClient*                      _client;
            osg::observer_ptr<LibVncImage>  _image;
            bool                            _done;

        };

    public:

        rfbClient* _client;

        osg::ref_ptr<RfbThread>     _rfbThread;

};

LibVncImage::LibVncImage():
    _timeOfLastRender(0), _active(true), _client(0), _rfbThread(0)
{
    // setPixelBufferObject(new osg::PixelBufferObject(this);

    _inactiveBlock = new osg::RefBlock;
    _inactiveBlock->release();
}

LibVncImage::~LibVncImage()
{
    close();
}

static rfbBool rfbInitConnection(rfbClient* client)
{
  /* Unless we accepted an incoming connection, make a TCP connection to the
     given VNC server */

  if (!client->listenSpecified) {
    if (!client->serverHost || !ConnectToRFBServer(client,client->serverHost,client->serverPort))
      return FALSE;
  }

  /* Initialise the VNC connection, including reading the password */

  if (!InitialiseRFBConnection(client))
    return FALSE;

  if (!SetFormatAndEncodings(client))
    return FALSE;

  client->width=client->si.framebufferWidth;
  client->height=client->si.framebufferHeight;
  client->MallocFrameBuffer(client);

  if (client->updateRect.x < 0) {
    client->updateRect.x = client->updateRect.y = 0;
    client->updateRect.w = client->width;
    client->updateRect.h = client->height;
  }

  if (client->appData.scaleSetting>1)
  {
      if (!SendScaleSetting(client, client->appData.scaleSetting))
          return FALSE;
      if (!SendFramebufferUpdateRequest(client,
                  client->updateRect.x / client->appData.scaleSetting,
                  client->updateRect.y / client->appData.scaleSetting,
                  client->updateRect.w / client->appData.scaleSetting,
                  client->updateRect.h / client->appData.scaleSetting,
                  FALSE))
          return FALSE;
  }
  else
  {
      if (!SendFramebufferUpdateRequest(client,
                  client->updateRect.x, client->updateRect.y,
                  client->updateRect.w, client->updateRect.h,
                  FALSE))
      return FALSE;
  }

  return TRUE;
}


bool LibVncImage::connect(const std::string& hostname)
{
    if (hostname.empty()) return false;

    if (_client) close();

    _client = rfbGetClient(8,3,4);
    _client->canHandleNewFBSize = TRUE;
    _client->MallocFrameBuffer = resizeImage;
    _client->GotFrameBufferUpdate = updateImage;
    _client->HandleKeyboardLedState = 0;
    _client->HandleTextChat = 0;

    rfbClientSetClientData(_client, 0, this);
    
    _client->serverHost = strdup(hostname.c_str());

    // _client->serverPort = ;
    // _client->appData.qualityLevel = ;
    // _client->appData.encodings = ;
    // _client->appData.compressLevel = ;
    // _client->appData.scaleSetting = ;

    if(rfbInitConnection(_client))
    {
        _rfbThread = new RfbThread(_client, this);
        _rfbThread->startThread();
        
        return true;
    }
    else
    {
        close();
        
        return false;
    }
}


void LibVncImage::close()
{
    // Allow the thread to run, so it can tidy up
    _active = true;
    _inactiveBlock->release();

    // stop the client thread
    _rfbThread = 0;

    if (_client)
    {
        // close the client
        rfbClientCleanup(_client);
        _client = 0;
    }
}


rfbBool LibVncImage::resizeImage(rfbClient* client) 
{
    osg::Image* image = (osg::Image*)(rfbClientGetClientData(client, 0));
    
    int width=client->width;
    int height=client->height;
    int depth=client->format.bitsPerPixel;

    osg::notify(osg::NOTICE)<<"resize "<<width<<", "<<height<<", "<<depth<<" image = "<<image<<std::endl;

    image->allocateImage(width,height,1,GL_RGBA,GL_UNSIGNED_BYTE);
    
    client->frameBuffer= (uint8_t*)(image->data());
    
    return TRUE;
}

void LibVncImage::updateImage(rfbClient* client,int x,int y,int w,int h)
{
    osg::Image* image = (osg::Image*)(rfbClientGetClientData(client, 0));
    // Inconveniently, RFB leaves the unused byte at zero
    for (int j=y; j < y+h; j++)
    for (int i=x; i < x+w; i++)
        *(image->data(i,j) + 3) = 255;
    image->dirty();
}

bool LibVncImage::sendPointerEvent(int x, int y, int buttonMask)
{
    if (_client)
    {
        SendPointerEvent(_client ,x, y, buttonMask);
        return true;
    }
    return false;
}

bool LibVncImage::sendKeyEvent(int key, bool keyDown)
{
    if (_client)
    {
        SendKeyEvent(_client, key, keyDown ? TRUE : FALSE);
        return true;
    }
    return false;
}


void LibVncImage::setFrameLastRendered(const osg::FrameStamp*)
{
    osg::notify(osg::NOTICE)<<"Rendered"<<std::endl;
    _timeOfLastRender = time();

    if (!_active) {
        // If the last frame rendered, ask for another one
        osg::notify(osg::NOTICE)<<"Activating as frame rendered"<<std::endl;
        _active = true;
        _inactiveBlock->release();
    }
}

void LibVncImage::updated()
{
    _timeOfLastUpdate = time();
}

class ReaderWriterVNC : public osgDB::ReaderWriter
{
    public:
    
        ReaderWriterVNC()
        {
            supportsExtension("vnc","VNC plugin");
        }
        
        virtual const char* className() const { return "VNC plugin"; }

        virtual osgDB::ReaderWriter::ReadResult readObject(const std::string& file, const osgDB::ReaderWriter::Options* options) const
        {
            return readImage(file,options);
        }

        virtual osgDB::ReaderWriter::ReadResult readImage(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const
        {
            if (!osgDB::equalCaseInsensitive(osgDB::getFileExtension(fileName),"vnc"))
            {
                return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
            }

            std::string hostname = osgDB::getNameLessExtension(osgDB::getSimpleFileName(fileName));
            
            osg::notify(osg::NOTICE)<<"Hostname = "<<hostname<<std::endl;

            osg::ref_ptr<LibVncImage> image = new LibVncImage;
            image->setDataVariance(osg::Object::DYNAMIC);
            image->setFileName(fileName);
            image->setOrigin(osg::Image::TOP_LEFT);

            if (!image->connect(hostname))
            {
                return "Could not connect to "+hostname;
            }

            // Wait until we at least know how big the image is
            while (!image->valid()) {
                osg::notify(osg::NOTICE)<<"Image still not valid"<<std::endl;
                OpenThreads::Thread::YieldCurrentThread();
            }
            
            return image.get();
        }
        
        virtual osgDB::ReaderWriter::ReadResult readNode(const std::string& fileName, const osgDB::ReaderWriter::Options* options) const
        {
            osgDB::ReaderWriter::ReadResult result = readImage(fileName, options);
            if (!result.validImage()) return result;
            
            osg::ref_ptr<osgWidget::VncClient> vncClient = new osgWidget::VncClient();
            if (vncClient->assign(dynamic_cast<osgWidget::VncImage*>(result.getImage())))
            {
                return vncClient.release();
            }
            else
            {
                return osgDB::ReaderWriter::ReadResult::FILE_NOT_HANDLED;
            }
        }
};

// now register with Registry to instantiate the above
// reader/writer.
REGISTER_OSGPLUGIN(vnc, ReaderWriterVNC)

_______________________________________________
osg-submissions mailing list
[email protected]
http://lists.openscenegraph.org/listinfo.cgi/osg-submissions-openscenegraph.org

Reply via email to