/*************************************************************************** * Copyright (C) 2004,5 Max Howell * * * * 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 #include //Vis::Selector #include //Vis::Selector #include //Vis::Selector #include //locateLocal() #include //Vis::Selector ctor #include "socketserver.h" #include #include #include #include #include //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(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( pApp->playlistWindow() ); TQObject *o = parent->child( "Vis::Selector::instance" ); debug() << bool(o == 0) << endl; return o ? static_cast( 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(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( 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( firstChild() ); item; item = static_cast( 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( firstChild() ); item; item = static_cast( 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( 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 #include 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( "
" "

No Visualizations Found

" "Possible reasons:" "
    " "
  • libvisual is not installed
  • " "
  • No libvisual plugins are installed
  • " "
" "Please check these possibilities and restart Amarok." "
" ), 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"