|
|
|
/***************************************************************************
|
|
|
|
* Copyright (C) 2004,5 Max Howell <max.howell@methylblue.com> *
|
|
|
|
* *
|
|
|
|
* 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. *
|
|
|
|
* *
|
|
|
|
***************************************************************************/
|
|
|
|
|
|
|
|
#define DEBUG_PREFIX "SocketServer"
|
|
|
|
|
|
|
|
#include "app.h"
|
|
|
|
#include "amarok.h"
|
|
|
|
#include "debug.h"
|
|
|
|
#include "enginebase.h" //to get the scope
|
|
|
|
#include "enginecontroller.h" //to get the engine
|
|
|
|
#include "statusbar.h"
|
|
|
|
#include <tdelocale.h>
|
|
|
|
#include <tdepopupmenu.h> //Vis::Selector
|
|
|
|
#include <kprocess.h> //Vis::Selector
|
|
|
|
#include <twin.h> //Vis::Selector
|
|
|
|
#include <kstandarddirs.h> //locateLocal()
|
|
|
|
#include <tqtooltip.h> //Vis::Selector ctor
|
|
|
|
#include "socketserver.h"
|
|
|
|
#include <sys/socket.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/un.h>
|
|
|
|
#include <vector>
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//TODO allow stop/start and pause signals to be sent to registered visualizations
|
|
|
|
//TODO allow transmission of visual data back to us here and allow that to be embedded in stuff
|
|
|
|
//TODO decide whether to use 16 bit integers or 32 bit floats as data sent to analyzers
|
|
|
|
//TODO allow visualizations to determine their own data sizes
|
|
|
|
|
|
|
|
/// @class Amarok::SocketServer
|
|
|
|
|
|
|
|
Amarok::SocketServer::SocketServer( const TQString &socketName, TQObject *parent )
|
|
|
|
: TQServerSocket( parent )
|
|
|
|
{
|
|
|
|
m_sockfd = ::socket( AF_UNIX, SOCK_STREAM, 0 );
|
|
|
|
|
|
|
|
if( m_sockfd == -1 ) {
|
|
|
|
warning() << "socket() error\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
m_path = locateLocal( "socket", socketName ).local8Bit();
|
|
|
|
|
|
|
|
union {
|
|
|
|
sockaddr_un un;
|
|
|
|
sockaddr sa;
|
|
|
|
} local;
|
|
|
|
local.un.sun_family = AF_UNIX;
|
|
|
|
qstrcpy( &local.un.sun_path[0], m_path );
|
|
|
|
::unlink( m_path ); //FIXME why do we delete it?
|
|
|
|
|
|
|
|
if( ::bind( m_sockfd, &local.sa, sizeof(local.un) ) == -1 ) {
|
|
|
|
warning() << "bind() error\n";
|
|
|
|
::close( m_sockfd );
|
|
|
|
m_sockfd = -1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( ::listen( m_sockfd, 1 ) == -1 ) {
|
|
|
|
warning() << "listen() error\n";
|
|
|
|
::close( m_sockfd );
|
|
|
|
m_sockfd = -1;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->setSocket( m_sockfd );
|
|
|
|
}
|
|
|
|
|
|
|
|
Amarok::SocketServer::~SocketServer()
|
|
|
|
{
|
|
|
|
if( m_sockfd != -1 )
|
|
|
|
::close( m_sockfd );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// @class Vis::SocketServer
|
|
|
|
|
|
|
|
Vis::SocketServer::SocketServer( TQObject *parent )
|
|
|
|
: Amarok::SocketServer( "amarok.visualization_socket", parent )
|
|
|
|
{}
|
|
|
|
|
|
|
|
void
|
|
|
|
Vis::SocketServer::newConnection( int sockfd )
|
|
|
|
{
|
|
|
|
debug() << "Connection requested: " << sockfd << endl;
|
|
|
|
new SocketNotifier( sockfd ); //handles its own memory
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// @class Vis::SocketNotifier
|
|
|
|
|
|
|
|
Vis::SocketNotifier::SocketNotifier( int sockfd )
|
|
|
|
: TQSocketNotifier( sockfd, TQSocketNotifier::Read, TQT_TQOBJECT(this) )
|
|
|
|
{
|
|
|
|
connect( this, TQT_SIGNAL(activated( int )), TQT_SLOT(request( int )) );
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Vis::SocketNotifier::request( int sockfd ) //slot
|
|
|
|
{
|
|
|
|
char buf[16]; //TODO docs should state request commands can only be 16 bytes
|
|
|
|
int nbytes = recv( sockfd, buf, 16, 0 );
|
|
|
|
|
|
|
|
if( nbytes > 0 )
|
|
|
|
{
|
|
|
|
TQCString result( buf );
|
|
|
|
|
|
|
|
if( result == "REG" )
|
|
|
|
{
|
|
|
|
pid_t *pid = reinterpret_cast<pid_t*>(buf + 4);
|
|
|
|
|
|
|
|
debug() << "Registration pid: " << *pid << endl;
|
|
|
|
|
|
|
|
Vis::Selector::instance()->mapPID( *pid, sockfd );
|
|
|
|
}
|
|
|
|
else if( result == "PCM" )
|
|
|
|
{
|
|
|
|
const Engine::Scope &scope = EngineController::engine()->scope();
|
|
|
|
|
|
|
|
::send( sockfd, &scope[0], scope.size()*sizeof(int16_t), 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
debug() << "recv() error, closing socket: " << sockfd << endl;
|
|
|
|
::close( sockfd );
|
|
|
|
delete this;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// @class Vis::Selector
|
|
|
|
|
|
|
|
Vis::Selector*
|
|
|
|
Vis::Selector::instance()
|
|
|
|
{
|
|
|
|
TQWidget *parent = reinterpret_cast<TQWidget*>( pApp->playlistWindow() );
|
|
|
|
TQObject *o = parent->child( "Vis::Selector::instance" );
|
|
|
|
|
|
|
|
debug() << bool(o == 0) << endl;
|
|
|
|
|
|
|
|
return o ? static_cast<Selector*>( TQT_TQWIDGET(o) ) : new Selector( parent );
|
|
|
|
}
|
|
|
|
|
|
|
|
Vis::Selector::Selector( TQWidget *parent )
|
|
|
|
: TQListView( parent, "Vis::Selector::instance", TQt::WType_Dialog )
|
|
|
|
, m_server( new SocketServer( TQT_TQOBJECT(this) ) )
|
|
|
|
{
|
|
|
|
Amarok::OverrideCursor waitcursor;
|
|
|
|
|
|
|
|
setCaption( kapp->makeStdCaption( i18n( "Visualizations" ) ) );
|
|
|
|
|
|
|
|
// Gives the window a small title bar, and skips a taskbar entry
|
|
|
|
KWin::setType( winId(), NET::Utility );
|
|
|
|
KWin::setState( winId(), NET::SkipTaskbar );
|
|
|
|
|
|
|
|
setSorting( 0 );
|
|
|
|
setColumnWidthMode( 0, TQListView::Maximum );
|
|
|
|
TQToolTip::add( viewport(), i18n( "Right-click on item for context menu" ) );
|
|
|
|
addColumn( TQString() );
|
|
|
|
addColumn( TQString() );
|
|
|
|
reinterpret_cast<TQWidget*>(header())->hide();
|
|
|
|
|
|
|
|
|
|
|
|
connect( this, TQT_SIGNAL(contextMenuRequested( TQListViewItem*, const TQPoint&, int )),
|
|
|
|
this, TQT_SLOT(rightButton( TQListViewItem*, const TQPoint&, int )) );
|
|
|
|
|
|
|
|
// Can I get a pointer to the data section of a TQCString?
|
|
|
|
char str[4096];
|
|
|
|
FILE* vis = popen( "amarok_libvisual --list", "r" );
|
|
|
|
str[ fread( static_cast<void*>( str ), sizeof(char), 4096, vis ) ] = '\0';
|
|
|
|
pclose( vis );
|
|
|
|
|
|
|
|
const TQStringList entries = TQStringList::split( '\n', TQString::fromLocal8Bit( str ) );
|
|
|
|
|
|
|
|
for( TQStringList::ConstIterator it = entries.begin(); it != entries.end(); ++it )
|
|
|
|
new Item( this, "amarok_libvisual", *it, "libvisual" );
|
|
|
|
|
|
|
|
resize( sizeHint() + TQSize(20,0) );
|
|
|
|
// Center the widget on screen
|
|
|
|
move( parentWidget()->width()/2 - width()/2, parentWidget()->height()/2 - height()/2 );
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Vis::Selector::processExited( TDEProcess *proc )
|
|
|
|
{
|
|
|
|
for( Item *item = static_cast<Item*>( firstChild() ); item; item = static_cast<Item*>( item->nextSibling() ) )
|
|
|
|
if( item->m_proc == proc )
|
|
|
|
item->setOn( false ); //will delete m_proc via stateChange( bool )
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shouldn't be necessary, but it's part of a fix to make libvisual work again when running with amarok binary
|
|
|
|
void
|
|
|
|
Vis::Selector::receivedStdout( TDEProcess */*proc*/, char* buffer, int length )
|
|
|
|
{
|
|
|
|
debug() << TQString::fromLatin1( buffer, length ) << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Vis::Selector::mapPID( int pid, int sockfd )
|
|
|
|
{
|
|
|
|
//TODO if we don't find the PID, request process plugin so we can assign the correct checkitem
|
|
|
|
|
|
|
|
for( Item *item = static_cast<Item*>( firstChild() ); item; item = static_cast<Item*>( item->nextSibling() ) )
|
|
|
|
if( item->m_proc && item->m_proc->pid() == pid )
|
|
|
|
{
|
|
|
|
item->m_sockfd = sockfd;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug() << "No matching pid in the Vis::Selector!\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Vis::Selector::rightButton( TQListViewItem* qitem, const TQPoint& pos, int )
|
|
|
|
{
|
|
|
|
//TODO if the vis is not running it cannot be configured and you shouldn't show the popupmenu!
|
|
|
|
|
|
|
|
if( !qitem )
|
|
|
|
return;
|
|
|
|
|
|
|
|
Item *item = static_cast<Item*>( qitem );
|
|
|
|
|
|
|
|
TDEPopupMenu menu( this );
|
|
|
|
menu.insertItem( i18n( "Fullscreen" ), 0 );
|
|
|
|
|
|
|
|
if( !item->m_proc || !item->m_proc->isRunning() )
|
|
|
|
menu.setItemEnabled( 0, false );
|
|
|
|
|
|
|
|
switch( menu.exec( pos ) ) {
|
|
|
|
case 0: ::send( item->m_sockfd, "fullscreen", 11, 0 ); break;
|
|
|
|
default: break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include <tqpainter.h>
|
|
|
|
#include <tqsimplerichtext.h>
|
|
|
|
void
|
|
|
|
Vis::Selector::viewportPaintEvent( TQPaintEvent *e )
|
|
|
|
{
|
|
|
|
if( childCount() == 0 ) {
|
|
|
|
|
|
|
|
//TODO the right message if amarok_libvisual is present but libvisual isn't
|
|
|
|
hide();
|
|
|
|
Amarok::StatusBar::instance()->longMessage( i18n(
|
|
|
|
"<div align=center>"
|
|
|
|
"<h3>No Visualizations Found</h3>"
|
|
|
|
"Possible reasons:"
|
|
|
|
"<ul>"
|
|
|
|
"<li>libvisual is not installed</li>"
|
|
|
|
"<li>No libvisual plugins are installed</li>"
|
|
|
|
"</ul>"
|
|
|
|
"Please check these possibilities and restart Amarok."
|
|
|
|
"</div>" ), KDE::StatusBar::Sorry );
|
|
|
|
}
|
|
|
|
else { TQListView::viewportPaintEvent( e ); }
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// @class Vis::Selector::Item
|
|
|
|
|
|
|
|
Vis::Selector::Item::~Item()
|
|
|
|
{
|
|
|
|
delete m_proc; //kills the process too
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
Vis::Selector::Item::stateChange( bool ) //SLOT
|
|
|
|
{
|
|
|
|
switch( state() ) {
|
|
|
|
case On:
|
|
|
|
m_proc = new Amarok::Process();
|
|
|
|
*m_proc << TDEStandardDirs::findExe( m_command )
|
|
|
|
<< Selector::instance()->m_server->path()
|
|
|
|
<< text( 0 );
|
|
|
|
|
|
|
|
connect( m_proc, TQT_SIGNAL(processExited( TDEProcess* )), listView(), TQT_SLOT(processExited( TDEProcess* )) );
|
|
|
|
// Shouldn't be necessary, but make visualizations work again when running with amarok binary
|
|
|
|
connect( m_proc, TQT_SIGNAL(receivedStdout (TDEProcess*, char*, int ) ), listView(), TQT_SLOT(receivedStdout (TDEProcess*, char*, int ) ) );
|
|
|
|
debug() << "Starting visualization..\n";
|
|
|
|
if( m_proc->start( TDEProcess::NotifyOnExit, TDEProcess::AllOutput ) )
|
|
|
|
break;
|
|
|
|
|
|
|
|
//ELSE FALL_THROUGH
|
|
|
|
|
|
|
|
warning() << "Could not start " << text( 0 ) << endl;
|
|
|
|
|
|
|
|
case Off:
|
|
|
|
debug() << "Stopping visualization\n";
|
|
|
|
|
|
|
|
delete m_proc;
|
|
|
|
m_proc = 0;
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#include "socketserver.moc"
|