1 /***************************************************************************
2 * Copyright (C) 2004,5 Max Howell <max.howell@methylblue.com> *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
9 ***************************************************************************/
11 #define DEBUG_PREFIX "SocketServer"
13 #include "socketserver.h"
18 #include "enginebase.h" //to get the scope
19 #include "enginecontroller.h" //to get the engine
21 #include "ContextStatusBar.h"
25 #include <KMenu> //Vis::Selector
26 #include <KStandardDirs> //locateLocal()
27 #include <KWindowSystem> //Vis::Selector
29 #include <Q3PopupMenu>
31 #include <QPaintEvent>
32 #include <QToolTip> //Vis::Selector ctor
34 #include <sys/socket.h>
35 #include <sys/types.h>
41 //TODO allow stop/start and pause signals to be sent to registered visualizations
42 //TODO allow transmission of visual data back to us here and allow that to be embedded in stuff
43 //TODO decide whether to use 16 bit integers or 32 bit floats as data sent to analyzers
44 //TODO allow visualizations to determine their own data sizes
46 /// @class Amarok::SocketServer
48 Amarok::SocketServer::SocketServer( const QString
&socketName
, QObject
*parent
)
49 : Q3ServerSocket( parent
)
51 m_sockfd
= ::socket( AF_UNIX
, SOCK_STREAM
, 0 );
53 if( m_sockfd
== -1 ) {
54 warning() << "socket() error\n";
58 m_path
= KStandardDirs::locateLocal( "socket", socketName
).toLocal8Bit();
64 local
.un
.sun_family
= AF_UNIX
;
65 qstrcpy( &local
.un
.sun_path
[0], m_path
);
66 ::unlink( m_path
); //FIXME why do we delete it?
68 if( ::bind( m_sockfd
, &local
.sa
, sizeof(local
.un
) ) == -1 ) {
69 warning() << "bind() error\n";
75 if( ::listen( m_sockfd
, 1 ) == -1 ) {
76 warning() << "listen() error\n";
82 this->setSocket( m_sockfd
);
85 Amarok::SocketServer::~SocketServer()
93 /// @class Vis::SocketServer
95 Vis::SocketServer::SocketServer( QObject
*parent
)
96 : Amarok::SocketServer( "amarok.visualization_socket", parent
)
100 Vis::SocketServer::newConnection( int sockfd
)
102 debug() << "Connection requested: " << sockfd
;
103 new SocketNotifier( sockfd
); //handles its own memory
108 /// @class Vis::SocketNotifier
110 Vis::SocketNotifier::SocketNotifier( int sockfd
)
111 : QSocketNotifier( sockfd
, QSocketNotifier::Read
, this )
113 connect( this, SIGNAL(activated( int )), SLOT(request( int )) );
117 Vis::SocketNotifier::request( int sockfd
) //slot
119 char buf
[16]; //TODO docs should state request commands can only be 16 bytes
120 int nbytes
= recv( sockfd
, buf
, 16, 0 );
124 QByteArray
result( buf
);
126 if( result
== "REG" )
128 pid_t
*pid
= reinterpret_cast<pid_t
*>(buf
+ 4);
130 debug() << "Registration pid: " << *pid
;
132 Vis::Selector::instance()->mapPID( *pid
, sockfd
);
134 else if( result
== "PCM" )
136 const Engine::Scope
&scope
= EngineController::engine()->scope();
138 ::send( sockfd
, (const char *)&scope
[0], scope
.size()*sizeof(int16_t), 0 );
142 debug() << "recv() error, closing socket: " << sockfd
;
150 /// @class Vis::Selector
153 Vis::Selector::instance()
155 QWidget
*parent
= reinterpret_cast<QWidget
*>( pApp
->mainWindow() );
156 QObject
*o
= parent
->findChild
<QObject
*>( "Vis::Selector::instance" );
158 debug() << bool(o
== 0);
160 return o
? static_cast<Selector
*>( o
) : new Selector( parent
);
163 Vis::Selector::Selector( QWidget
*parent
)
164 : Q3ListView( parent
, "Vis::Selector::instance", Qt::WType_Dialog
)
165 , m_server( new SocketServer( this ) )
167 Amarok::OverrideCursor waitcursor
;
169 setCaption( KDialog::makeStandardCaption( i18n( "Visualizations" ) ) );
171 // Gives the window a small title bar, and skips a taskbar entry
173 KWindowSystem::setType( winId(), NET::Utility
);
174 KWindowSystem::setState( winId(), NET::SkipTaskbar
);
178 setColumnWidthMode( 0, Q3ListView::Maximum
);
179 viewport()->setToolTip( i18n( "Right-click on item for context menu" ) );
180 addColumn( QString() );
181 addColumn( QString() );
182 reinterpret_cast<QWidget
*>(header())->hide();
185 connect( this, SIGNAL(contextMenuRequested( Q3ListViewItem
*, const QPoint
&, int )),
186 this, SLOT(rightButton( Q3ListViewItem
*, const QPoint
&, int )) );
188 // Can I get a pointer to the data section of a QCString?
190 FILE* vis
= popen( "amarok_libvisual --list", "r" );
191 str
[ fread( static_cast<void*>( str
), sizeof(char), 4096, vis
) ] = '\0';
194 const QStringList entries
= QStringList::split( '\n', QString::fromLocal8Bit( str
) );
196 for( QStringList::ConstIterator it
= entries
.begin(); it
!= entries
.end(); ++it
)
197 new Item( this, "amarok_libvisual", *it
, "libvisual" );
199 resize( sizeHint() + QSize(20,0) );
200 // Center the widget on screen
201 move( parentWidget()->width()/2 - width()/2, parentWidget()->height()/2 - height()/2 );
205 Vis::Selector::processExited( Process
*proc
)
207 for( Item
*item
= static_cast<Item
*>( firstChild() ); item
; item
= static_cast<Item
*>( item
->nextSibling() ) )
208 if( item
->m_proc
== proc
)
209 item
->setOn( false ); //will delete m_proc via stateChange( bool )
212 // Shouldn't be necessary, but it's part of a fix to make libvisual work again when running with amarok binary
214 Vis::Selector::receivedStdout( Process
*proc
)
216 debug() << QString::fromLatin1( proc
->readAllStandardOutput() );
220 Vis::Selector::mapPID( int pid
, int sockfd
)
222 //TODO if we don't find the PID, request process plugin so we can assign the correct checkitem
224 for( Item
*item
= static_cast<Item
*>( firstChild() ); item
; item
= static_cast<Item
*>( item
->nextSibling() ) )
225 if( item
->m_proc
&& item
->m_proc
->pid() == pid
)
227 item
->m_sockfd
= sockfd
;
231 debug() << "No matching pid in the Vis::Selector!\n";
235 Vis::Selector::rightButton( Q3ListViewItem
* qitem
, const QPoint
& pos
, int )
237 //TODO if the vis is not running it cannot be configured and you shouldn't show the popupmenu!
242 Item
*item
= static_cast<Item
*>( qitem
);
244 Q3PopupMenu
menu( this );
245 menu
.insertItem( i18n( "Fullscreen" ), 0 );
247 if( !item
->m_proc
|| item
->m_proc
->state() == Process::Running
)
248 menu
.setItemEnabled( 0, false );
250 switch( menu
.exec( pos
) ) {
251 case 0: ::send( item
->m_sockfd
, "fullscreen", 11, 0 ); break;
257 #include <q3simplerichtext.h>
259 Vis::Selector::viewportPaintEvent( QPaintEvent
*e
)
261 if( childCount() == 0 ) {
263 //TODO the right message if amarok_libvisual is present but libvisual isn't
265 Amarok::ContextStatusBar::instance()->longMessage( i18n(
267 "<h3>No Visualizations Found</h3>"
270 "<li>libvisual is not installed</li>"
271 "<li>No libvisual plugins are installed</li>"
273 "Please check these possibilities and restart Amarok."
274 "</div>" ), KDE::StatusBar::Sorry
);
276 else { Q3ListView::viewportPaintEvent( e
); }
281 /// @class Vis::Selector::Item
283 Vis::Selector::Item::~Item()
285 delete m_proc
; //kills the process too
289 Vis::Selector::Item::stateChange( bool ) //SLOT
293 m_proc
= new Process();
294 m_proc
->setOutputChannelMode( Process::MergedChannels
);
295 *m_proc
<< KStandardDirs::findExe( m_command
)
296 << Selector::instance()->m_server
->path()
299 connect( m_proc
, SIGNAL(processExited( Process
* )), listView(), SLOT(processExited( Process
* )) );
300 // Shouldn't be necessary, but make visualizations work again when running with amarok binary
301 connect( m_proc
, SIGNAL(readyReceiveStandardOutput( ) ), listView(), SLOT(receivedStdout ( Process
* ) ) );
302 debug() << "Starting visualization..\n";
304 if( m_proc
->error() == Process::UnknownError
) // success
309 warning() << "Could not start " << text( 0 );
312 debug() << "Stopping visualization\n";
324 #include "socketserver.moc"