You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
qt3/tools/qvfb/qvfbview.cpp

616 lines
15 KiB

/**********************************************************************
** Copyright (C) 1992-2008 Trolltech ASA. All rights reserved.
**
** This file is part of Qt/Embedded virtual framebuffer.
**
** This file may be used under the terms of the GNU General
** Public License versions 2.0 or 3.0 as published by the Free
** Software Foundation and appearing in the files LICENSE.GPL2
** and LICENSE.GPL3 included in the packaging of this file.
** Alternatively you may (at your option) use any later version
** of the GNU General Public License if such license has been
** publicly approved by Trolltech ASA (or its successors, if any)
** and the KDE Free Qt Foundation.
**
** Please review the following information to ensure GNU General
** Public Licensing requirements will be met:
** http://trolltech.com/products/qt/licenses/licensing/opensource/.
** If you are unsure which license is appropriate for your use, please
** review the following information:
** http://trolltech.com/products/qt/licenses/licensing/licensingoverview
** or contact the sales department at sales@trolltech.com.
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with
** the Software.
**
** This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
** INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE. Trolltech reserves all rights not granted
** herein.
**
**********************************************************************/
#include "qglobal.h"
#if !defined( Q_WS_QWS ) || defined( QT_NO_QWS_MULTIPROCESS )
#define QLock QWSSemaphore
#undef QT_NO_QWS_MULTIPROCESS
#include "../../src/kernel/qlock.cpp"
#else
#include "qlock_p.h"
#endif
#include "qvfbview.h"
#include "qvfbhdr.h"
#define QTE_PIPE "QtEmbedded-%1"
#include <qapplication.h>
#include <qimage.h>
#include <qbitmap.h>
#include <qtimer.h>
#include <qwmatrix.h>
#include <qpainter.h>
#include "qanimationwriter.h"
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <math.h>
//#define QT_QWS_EXPERIMENTAL_REVERSE_BIT_ENDIANNESS
QVFbView::QVFbView( int display_id, int w, int h, int d, QWidget *parent,
const char *name, uint flags )
: QScrollView( parent, name, flags ), emulateTouchscreen(FALSE), qwslock(NULL)
{
displayid = display_id;
viewport()->setMouseTracking( TRUE );
viewport()->setFocusPolicy( StrongFocus );
zm = 1;
animation = 0;
int actualdepth=d;
switch ( d ) {
case 12:
actualdepth=16;
break;
case 1:
case 4:
case 8:
case 16:
case 32:
break;
default:
qFatal( "Unsupported bit depth %d\n", d );
}
mousePipe = QString(QT_VFB_MOUSE_PIPE).arg(display_id);
keyboardPipe = QString(QT_VFB_KEYBOARD_PIPE).arg(display_id);
unlink( mousePipe.latin1() );
mkfifo( mousePipe.latin1(), 0666 );
mouseFd = open( mousePipe.latin1(), O_RDWR | O_NDELAY );
if ( mouseFd == -1 ) {
qFatal( "Cannot open mouse pipe" );
}
unlink( keyboardPipe );
mkfifo( keyboardPipe, 0666 );
keyboardFd = open( keyboardPipe, O_RDWR | O_NDELAY );
if ( keyboardFd == -1 ) {
qFatal( "Cannot open keyboard pipe" );
}
key_t key = ftok( mousePipe.latin1(), 'b' );
int bpl;
if ( d == 1 )
bpl = (w*d+7)/8;
else
bpl = ((w*actualdepth+31)/32)*4;
int dataSize = bpl * h + sizeof( QVFbHeader );
shmId = shmget( key, dataSize, IPC_CREAT|0666);
if ( shmId != -1 )
data = (unsigned char *)shmat( shmId, 0, 0 );
else {
struct shmid_ds shm;
shmctl( shmId, IPC_RMID, &shm );
shmId = shmget( key, dataSize, IPC_CREAT|0666);
data = (unsigned char *)shmat( shmId, 0, 0 );
}
if ( (long)data == -1 )
qFatal( "Cannot attach to shared memory" );
hdr = (QVFbHeader *)data;
hdr->width = w;
hdr->height = h;
viewdepth = d;
hdr->depth = actualdepth;
hdr->linestep = bpl;
hdr->numcols = 0;
hdr->dataoffset = sizeof( QVFbHeader );
hdr->update = QRect();
resizeContents( w, h );
timer = new QTimer( this );
connect( timer, SIGNAL(timeout()), this, SLOT(timeout()) );
gammatable=0;
setGamma(1.0,1.0,1.0);
setRate( 30 );
}
QVFbView::~QVFbView()
{
stopAnimation();
sendKeyboardData( 0, 0, 0, TRUE, FALSE ); // magic die key
delete qwslock;
struct shmid_ds shm;
shmdt( (char*)data );
shmctl( shmId, IPC_RMID, &shm );
::close( mouseFd );
::close( keyboardFd );
unlink( mousePipe );
unlink( keyboardPipe );
}
QSize QVFbView::sizeHint() const
{
int f = 2 * frameWidth();
return QSize( contentsWidth() + f, contentsHeight() + f );
}
void QVFbView::setGamma(double gr, double gg, double gb)
{
if ( viewdepth < 12 )
return; // not implemented
gred=gr; ggreen=gg; gblue=gb;
switch ( viewdepth ) {
case 12:
rsh = 12;
gsh = 7;
bsh = 1;
rmax = 15;
gmax = 15;
bmax = 15;
break;
case 16:
rsh = 11;
gsh = 5;
bsh = 0;
rmax = 31;
gmax = 63;
bmax = 31;
break;
case 32:
rsh = 16;
gsh = 8;
bsh = 0;
rmax = 255;
gmax = 255;
bmax = 255;
}
int mm = QMAX(rmax,QMAX(gmax,bmax))+1;
if ( gammatable )
delete [] gammatable;
gammatable = new QRgb[mm];
for (int i=0; i<mm; i++) {
int r = int(pow(i,gr)*255/rmax);
int g = int(pow(i,gg)*255/gmax);
int b = int(pow(i,gb)*255/bmax);
if ( r > 255 ) r = 255;
if ( g > 255 ) g = 255;
if ( b > 255 ) b = 255;
gammatable[i] = qRgb(r,g,b);
//qDebug("%d: %d,%d,%d",i,r,g,b);
}
setDirty(rect());
}
void QVFbView::getGamma(int i, QRgb& rgb)
{
if ( i > 255 ) i = 255;
if ( i < 0 ) i = 0;
rgb = qRgb(qRed(gammatable[i*rmax/255]),
qGreen(gammatable[i*rmax/255]),
qBlue(gammatable[i*rmax/255]));
}
int QVFbView::displayId() const
{
return displayid;
}
int QVFbView::displayWidth() const
{
return hdr->width;
}
int QVFbView::displayHeight() const
{
return hdr->height;
}
int QVFbView::displayDepth() const
{
return viewdepth;
}
void QVFbView::setZoom( double z )
{
if ( zm != z ) {
zm = z;
setDirty(QRect(0,0,hdr->width,hdr->height));
resizeContents( int(hdr->width*z), int(hdr->height*z) );
updateGeometry();
qApp->sendPostedEvents();
topLevelWidget()->adjustSize();
drawScreen();
}
}
void QVFbView::setRate( int r )
{
refreshRate = r;
timer->start( 1000/r );
}
#ifndef Q_WS_QWS
// Get the name of the directory where Qt/Embedded temporary data should
// live.
static QString qws_dataDir()
{
QString username = "unknown";
const char *logname = getenv("LOGNAME");
if ( logname )
username = logname;
QString dataDir = "/tmp/qtembedded-" + username;
if ( mkdir( dataDir.latin1(), 0700 ) ) {
if ( errno != EEXIST ) {
qFatal( QString("Cannot create Qt/Embedded data directory: %1")
.arg( dataDir ) );
}
}
struct stat buf;
if ( lstat( dataDir.latin1(), &buf ) )
qFatal( QString( "stat failed for Qt/Embedded data directory: %1" )
.arg( dataDir ) );
if ( !S_ISDIR( buf.st_mode ) )
qFatal( QString( "%1 is not a directory" ).arg( dataDir ) );
if ( buf.st_uid != getuid() )
qFatal( QString( "Qt/Embedded data directory is not owned by user %1" )
.arg( getuid() ) );
if ( (buf.st_mode & 0677) != 0600 )
qFatal( QString( "Qt/Embedded data directory has incorrect permissions: %1" )
.arg( dataDir ) );
dataDir += "/";
return dataDir;
}
#endif
void QVFbView::initLock()
{
QString username = "unknown";
const char *logname = getenv("LOGNAME");
if ( logname )
username = logname;
qwslock = new QLock(qws_dataDir() + QString( QTE_PIPE ).arg( displayid ),
'd', TRUE);
}
void QVFbView::lock()
{
if ( !qwslock )
initLock();
qwslock->lock(QLock::Read);
}
void QVFbView::unlock()
{
if ( qwslock )
qwslock->unlock();
}
void QVFbView::sendMouseData( const QPoint &pos, int buttons )
{
write( mouseFd, &pos, sizeof( QPoint ) );
write( mouseFd, &buttons, sizeof( int ) );
}
void QVFbView::sendKeyboardData( int unicode, int keycode, int modifiers,
bool press, bool repeat )
{
QVFbKeyData kd;
kd.unicode = unicode | (keycode << 16);
kd.modifiers = modifiers;
kd.press = press;
kd.repeat = repeat;
write( keyboardFd, &kd, sizeof( QVFbKeyData ) );
}
void QVFbView::timeout()
{
lock();
if ( animation ) {
QRect r( hdr->update );
r = r.intersect( QRect(0, 0, hdr->width, hdr->height ) );
if ( r.isEmpty() ) {
animation->appendBlankFrame();
} else {
int l;
QImage img = getBuffer( r, l );
animation->appendFrame(img,QPoint(r.x(),r.y()));
}
}
if ( hdr->dirty ) {
drawScreen();
}
unlock();
}
QImage QVFbView::getBuffer( const QRect &r, int &leading ) const
{
switch ( viewdepth ) {
case 12:
case 16: {
static unsigned char *imgData = 0;
if ( !imgData ) {
int bpl = ((hdr->width*32+31)/32)*4;
imgData = new unsigned char [ bpl * hdr->height ];
}
QImage img( imgData, r.width(), r.height(), 32, 0, 0, QImage::IgnoreEndian );
const int rsh = viewdepth == 12 ? 12 : 11;
const int gsh = viewdepth == 12 ? 7 : 5;
const int bsh = viewdepth == 12 ? 1 : 0;
const int rmax = viewdepth == 12 ? 15 : 31;
const int gmax = viewdepth == 12 ? 15 : 63;
const int bmax = viewdepth == 12 ? 15 : 31;
for ( int row = 0; row < r.height(); row++ ) {
QRgb *dptr = (QRgb*)img.scanLine( row );
ushort *sptr = (ushort*)(data + hdr->dataoffset + (r.y()+row)*hdr->linestep);
sptr += r.x();
#ifdef QT_QWS_REVERSE_BYTE_ENDIANNESS
for ( int col=0; col < r.width()/2; col++ ) {
#else
for ( int col=0; col < r.width(); col++ ) {
#endif
ushort s = *sptr++;
#ifdef QT_QWS_REVERSE_BYTE_ENDIANNESS
ushort s2 = *sptr++;
*dptr++ = qRgb(qRed(gammatable[(s2>>rsh)&rmax]),qGreen(gammatable[(s2>>gsh)&gmax]),qBlue(gammatable[(s2>>bsh)&bmax]));
#endif
*dptr++ = qRgb(qRed(gammatable[(s>>rsh)&rmax]),qGreen(gammatable[(s>>gsh)&gmax]),qBlue(gammatable[(s>>bsh)&bmax]));
//*dptr++ = qRgb(((s>>rsh)&rmax)*255/rmax,((s>>gsh)&gmax)*255/gmax,((s>>bsh)&bmax)*255/bmax);
}
}
leading = 0;
return img;
}
case 4: {
static unsigned char *imgData = 0;
if ( !imgData ) {
int bpl = ((hdr->width*8+31)/32)*4;
imgData = new unsigned char [ bpl * hdr->height ];
}
QImage img( imgData, r.width(), r.height(), 8, hdr->clut, 16,
QImage::IgnoreEndian );
for ( int row = 0; row < r.height(); row++ ) {
unsigned char *dptr = img.scanLine( row );
unsigned char *sptr = data + hdr->dataoffset + (r.y()+row)*hdr->linestep;
sptr += r.x()/2;
int col = 0;
#ifdef QT_QWS_EXPERIMENTAL_REVERSE_BIT_ENDIANNESS
if ( r.x() & 1 ) {
*dptr++ = *sptr++ & 0x0f;
col++;
}
for ( ; col < r.width()-1; col+=2 ) {
unsigned char s = *sptr++;
*dptr++ = s >> 4;
*dptr++ = s & 0x0f;
}
if ( !(r.right() & 1) )
*dptr = *sptr >> 4;
#else
if ( r.x() & 1 ) {
*dptr++ = *sptr++ >> 4;
col++;
}
for ( ; col < r.width()-1; col+=2 ) {
unsigned char s = *sptr++;
*dptr++ = s & 0x0f;
*dptr++ = s >> 4;
}
if ( !(r.right() & 1) )
*dptr = *sptr & 0x0f;
#endif
}
leading = 0;
return img;
}
case 32: {
leading = r.x();
return QImage( data + hdr->dataoffset + r.y() * hdr->linestep,
hdr->width, r.height(), hdr->depth, 0,
0, QImage::LittleEndian );
}
case 8: {
leading = r.x();
return QImage( data + hdr->dataoffset + r.y() * hdr->linestep,
hdr->width, r.height(), hdr->depth, hdr->clut,
256, QImage::LittleEndian );
}
case 1: {
leading = r.x();
return QImage( data + hdr->dataoffset + r.y() * hdr->linestep,
hdr->width, r.height(), hdr->depth, hdr->clut,
#ifndef QT_QWS_EXPERIMENTAL_REVERSE_BIT_ENDIANNESS
0, QImage::LittleEndian );
#else
0, QImage::BigEndian );
#endif
}
}
return QImage();
}
void QVFbView::drawScreen()
{
QPainter p( viewport() );
p.translate( -contentsX(), -contentsY() );
lock();
QRect r( hdr->update );
hdr->dirty = FALSE;
hdr->update = QRect();
// qDebug( "update %d, %d, %dx%d", r.y(), r.x(), r.width(), r.height() );
r = r.intersect( QRect(0, 0, hdr->width, hdr->height ) );
if ( !r.isEmpty() ) {
if ( int(zm) != zm ) {
r.rLeft() = int(int(r.left()*zm)/zm);
r.rTop() = int(int(r.top()*zm)/zm);
r.rRight() = int(int(r.right()*zm+zm+0.0000001)/zm+1.9999);
r.rBottom() = int(int(r.bottom()*zm+zm+0.0000001)/zm+1.9999);
r.rRight() = QMIN(r.right(),hdr->width-1);
r.rBottom() = QMIN(r.bottom(),hdr->height-1);
}
int leading;
QImage img( getBuffer( r, leading ) );
QPixmap pm;
if ( zm == 1 ) {
pm.convertFromImage( img );
} else if ( int(zm) == zm ) {
QWMatrix m;
m.scale(zm,zm);
pm.convertFromImage( img );
pm = pm.xForm(m);
} else {
pm.convertFromImage( img.smoothScale(int(img.width()*zm),int(img.height()*zm)) );
}
unlock();
p.setPen( black );
p.setBrush( white );
p.drawPixmap( int(r.x()*zm), int(r.y()*zm), pm,
int(leading*zm), 0, pm.width(), pm.height() );
} else {
unlock();
}
}
bool QVFbView::eventFilter( QObject *obj, QEvent *e )
{
if ( obj == viewport() &&
(e->type() == QEvent::FocusIn || e->type() == QEvent::FocusOut) )
return TRUE;
return QScrollView::eventFilter( obj, e );
}
void QVFbView::viewportPaintEvent( QPaintEvent *pe )
{
QRect r( pe->rect() );
r.moveBy( contentsX(), contentsY() );
r = QRect(int(r.x()/zm),int(r.y()/zm),
int(r.width()/zm)+1,int(r.height()/zm)+1);
setDirty(r);
drawScreen();
}
void QVFbView::setDirty( const QRect& r )
{
lock();
hdr->update |= r;
hdr->dirty = TRUE;
unlock();
}
void QVFbView::contentsMousePressEvent( QMouseEvent *e )
{
sendMouseData( e->pos()/zm, e->stateAfter() );
}
void QVFbView::contentsMouseDoubleClickEvent( QMouseEvent *e )
{
sendMouseData( e->pos()/zm, e->stateAfter() );
}
void QVFbView::contentsMouseReleaseEvent( QMouseEvent *e )
{
sendMouseData( e->pos()/zm, e->stateAfter() );
}
void QVFbView::contentsMouseMoveEvent( QMouseEvent *e )
{
if ( !emulateTouchscreen || (e->state() & MouseButtonMask ) )
sendMouseData( e->pos()/zm, e->state() );
}
void QVFbView::keyPressEvent( QKeyEvent *e )
{
sendKeyboardData(e->text()[0].unicode(), e->key(),
e->stateAfter()&(ShiftButton|ControlButton|AltButton),
TRUE, e->isAutoRepeat());
}
void QVFbView::keyReleaseEvent( QKeyEvent *e )
{
sendKeyboardData(e->ascii(), e->key(),
e->stateAfter()&(ShiftButton|ControlButton|AltButton),
FALSE, e->isAutoRepeat());
}
QImage QVFbView::image() const
{
((QVFbView*)this)->lock();
int l;
QImage r = getBuffer( QRect(0, 0, hdr->width, hdr->height), l ).copy();
((QVFbView*)this)->unlock();
return r;
}
void QVFbView::startAnimation( const QString& filename )
{
delete animation;
animation = new QAnimationWriter(filename,"MNG");
animation->setFrameRate(refreshRate);
animation->appendFrame(QImage(data + hdr->dataoffset,
hdr->width, hdr->height, hdr->depth, hdr->clut,
256, QImage::LittleEndian));
}
void QVFbView::stopAnimation()
{
delete animation;
animation = 0;
}
void QVFbView::setTouchscreenEmulation( bool b )
{
emulateTouchscreen = b;
}