Salome HOME
6bb4668f292d901991548228b155ee9b0cb3b327
[modules/gui.git] / src / Session / SALOME_Session_Server.cxx
1 //  Copyright (C) 2007-2008  CEA/DEN, EDF R&D, OPEN CASCADE
2 //
3 //  Copyright (C) 2003-2007  OPEN CASCADE, EADS/CCR, LIP6, CEA/DEN,
4 //  CEDRAT, EDF R&D, LEG, PRINCIPIA R&D, BUREAU VERITAS
5 //
6 //  This library is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU Lesser General Public
8 //  License as published by the Free Software Foundation; either
9 //  version 2.1 of the License.
10 //
11 //  This library is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 //  Lesser General Public License for more details.
15 //
16 //  You should have received a copy of the GNU Lesser General Public
17 //  License along with this library; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
19 //
20 //  See http://www.salome-platform.org/ or email : webmaster.salome@opencascade.com
21 //
22 //  SALOME Session : implementation of Session.idl
23 //  File : SALOME_Session_Server.cxx
24 //  Author : Paul RASCLE, EDF
25 //  Module : SALOME
26 //
27 #include <Container_init_python.hxx>
28 #include <Utils_ORB_INIT.hxx>
29 #include <Utils_SINGLETON.hxx>
30 #include <SALOME_NamingService.hxx>
31 #include <SALOME_ModuleCatalog_impl.hxx>
32 #include <OpUtil.hxx>
33 #include <RegistryService.hxx>
34 #include <ConnectionManager_i.hxx>
35 #include <SALOME_LifeCycleCORBA.hxx>
36
37 #ifdef ENABLE_TESTRECORDER
38   #include <TestApplication.h>
39 #endif
40
41 #include <QDir>
42 #include <QFile>
43 #include <QApplication>
44 #include <QMutex>
45 #include <QWaitCondition>
46 #include <QRegExp>
47 #include <QTextStream>
48
49 #include <Utils_SALOME_Exception.hxx>
50 #include <Utils_CorbaException.hxx>
51 #include <SALOME_Event.h>
52
53 #include <SALOMEconfig.h>
54 #include CORBA_SERVER_HEADER(SALOME_Session)
55 #include CORBA_SERVER_HEADER(SALOMEDS)
56
57 #include <utilities.h>
58 #include "Session_ServerLauncher.hxx"
59 #include "Session_ServerCheck.hxx"
60
61 #include <QtxSplash.h>
62 #include <Style_Salome.h>
63 #include <SUIT_Tools.h>
64 #include <SUIT_Session.h>
65 #include <SUIT_Application.h>
66 #include <SUIT_Desktop.h>
67 #include <SUIT_ResourceMgr.h>
68 #include <SUIT_ExceptionHandler.h>
69
70 #include <Standard_Version.hxx>
71
72 /*! - read arguments, define list of server to launch with their arguments.
73  * - wait for naming service
74  * - create and run a thread for launch of all servers
75  *
76 */
77
78 //! CORBA server for SALOME Session
79 /*!
80  * SALOME_Session Server launches a SALOME session servant.
81  * The servant registers to the Naming Service.
82  * See SALOME_Session.idl for interface specification.
83  *
84  * Main services offered by the servant are:
85  * - launch GUI
86  * - stop Session ( must be idle )
87  * - get session state
88  */
89
90 PyObject* salome_shared_modules_module = 0;
91
92 void MessageOutput( QtMsgType type, const char* msg )
93 {
94   switch ( type )
95   {
96   case QtDebugMsg:
97     MESSAGE( "Debug: " << msg );
98     break;
99   case QtWarningMsg:
100     MESSAGE( "Warning: " << msg );
101     break;
102   case QtFatalMsg:
103     MESSAGE( "Fatal: " << msg );
104     break;
105   }
106 }
107
108 /* XPM */
109 static const char* pixmap_not_found_xpm[] = {
110 "16 16 3 1",
111 "       c None",
112 ".      c #000000",
113 "+      c #A80000",
114 "                ",
115 "                ",
116 "    .     .     ",
117 "   .+.   .+.    ",
118 "  .+++. .+++.   ",
119 "   .+++.+++.    ",
120 "    .+++++.     ",
121 "     .+++.      ",
122 "    .+++++.     ",
123 "   .+++.+++.    ",
124 "  .+++. .+++.   ",
125 "   .+.   .+.    ",
126 "    .     .     ",
127 "                ",
128 "                ",
129 "                "};
130
131 QString salomeVersion()
132 {
133   QString path( ::getenv( "GUI_ROOT_DIR" ) );
134   if ( !path.isEmpty() )
135     path += QDir::separator();
136   path += QString( "bin/salome/VERSION" );
137
138   QFile vf( path );
139   if ( !vf.open( QIODevice::ReadOnly ) )
140     return QString();
141
142   QString line( vf.readLine( 1024 ) );
143
144   vf.close();
145
146   if ( line.isEmpty() )
147     return QString();
148
149   while ( !line.isEmpty() && line.at( line.length() - 1 ) == QChar( '\n' ) )
150     line.remove( line.length() - 1, 1 );
151
152   QString ver;
153   int idx = line.lastIndexOf( ":" );
154   if ( idx != -1 )
155     ver = line.mid( idx + 1 ).trimmed();
156
157   return ver;
158 }
159
160 class SALOME_ResourceMgr : public SUIT_ResourceMgr
161 {
162 public:
163   SALOME_ResourceMgr( const QString& app, const QString& resVarTemplate ) : SUIT_ResourceMgr( app, resVarTemplate )
164   {
165     setCurrentFormat( "xml" );
166     setOption( "translators", QString( "%P_msg_%L.qm|%P_icons.qm|%P_images.qm" ) );
167     setDefaultPixmap( QPixmap( pixmap_not_found_xpm ) );
168   }
169   static void initResourceMgr()
170   {
171     if ( myExtAppName.isNull() || myExtAppVersion.isNull() ) {
172       SALOME_ResourceMgr resMgr( "SalomeApp", QString( "%1Config" ) );
173       resMgr.loadLanguage( "LightApp",  "en" );
174       resMgr.loadLanguage( "SalomeApp", "en" );
175
176       myExtAppName = QObject::tr( "APP_NAME" ).trimmed();
177       if ( myExtAppName == "APP_NAME" || myExtAppName.toLower() == "salome" ) 
178         myExtAppName = "SalomeApp";
179       myExtAppVersion = QObject::tr( "APP_VERSION" );
180       if ( myExtAppVersion == "APP_VERSION" ) {
181         if ( myExtAppName != "SalomeApp" )
182           myExtAppVersion = "";
183         else myExtAppVersion = salomeVersion();
184       }
185     }
186   }
187   QString version() const { return myExtAppVersion; }
188
189 protected:
190   QString userFileName( const QString& appName, const bool for_load ) const
191   { 
192     if ( version().isEmpty()  ) return ""; 
193     return SUIT_ResourceMgr::userFileName( myExtAppName, for_load );
194   }
195
196   virtual int userFileId( const QString& _fname ) const
197   {
198     if ( !myExtAppName.isEmpty() ) {
199       QRegExp exp( QString( "\\.%1rc\\.([a-zA-Z0-9.]+)$" ).arg( myExtAppName ) );
200       QRegExp vers_exp( "^([0-9]+)([A-Za-z]?)([0-9]*)$" );
201       
202       QString fname = QFileInfo( _fname ).fileName();
203       if( exp.exactMatch( fname ) ) {
204         QStringList vers = exp.cap( 1 ).split( ".", QString::SkipEmptyParts );
205         int major=0, minor=0;
206         major = vers[0].toInt();
207         minor = vers[1].toInt();
208         if( vers_exp.indexIn( vers[2] )==-1 )
209           return -1;
210         int release = 0, dev1 = 0, dev2 = 0;
211         release = vers_exp.cap( 1 ).toInt();
212         dev1 = vers_exp.cap( 2 )[ 0 ].toLatin1();
213         dev2 = vers_exp.cap( 3 ).toInt();
214         
215         int dev = dev1*100+dev2, id = major;
216         id*=100; id+=minor;
217         id*=100; id+=release;
218         id*=10000;
219         if ( dev > 0 ) id+=dev-10000;
220         return id;
221       }
222     }
223
224     return -1;
225   }
226
227 public:
228   static QString myExtAppName;
229   static QString myExtAppVersion;
230 };
231
232 QString SALOME_ResourceMgr::myExtAppName    = QString();
233 QString SALOME_ResourceMgr::myExtAppVersion = QString();
234
235 class SALOME_Session : public SUIT_Session
236 {
237 public:
238   SALOME_Session() : SUIT_Session() {}
239   virtual ~SALOME_Session() {}
240
241 protected:
242   virtual SUIT_ResourceMgr* createResourceMgr( const QString& appName ) const
243   {
244     SALOME_ResourceMgr::initResourceMgr();
245     SALOME_ResourceMgr* resMgr = new SALOME_ResourceMgr( appName, QString( "%1Config" ) );
246     return resMgr;
247   }
248 };
249
250 #ifdef ENABLE_TESTRECORDER
251   class SALOME_QApplication : public TestApplication
252 #else
253   class SALOME_QApplication : public QApplication
254 #endif
255 {
256 public:
257 #ifdef ENABLE_TESTRECORDER
258   SALOME_QApplication( int& argc, char** argv ) : TestApplication( argc, argv ), myHandler ( 0 ) {}
259 #else
260   SALOME_QApplication( int& argc, char** argv ) : QApplication( argc, argv ), myHandler ( 0 ) {}
261 #endif
262
263   virtual bool notify( QObject* receiver, QEvent* e )
264   {
265 #if (OCC_VERSION_MAJOR << 16 | OCC_VERSION_MINOR << 8 | OCC_VERSION_MAINTENANCE) < 0x060101
266     // Disable GUI user actions while python command is executed
267     if (SUIT_Session::IsPythonExecuted()) {
268       // Disable mouse and keyboard events
269       QEvent::Type aType = e->type();
270       if (aType == QEvent::MouseButtonPress || aType == QEvent::MouseButtonRelease ||
271           aType == QEvent::MouseButtonDblClick || aType == QEvent::MouseMove ||
272           aType == QEvent::Wheel || aType == QEvent::ContextMenu ||
273           aType == QEvent::KeyPress || aType == QEvent::KeyRelease ||
274           aType == QEvent::Accel || aType == QEvent::AccelOverride)
275         return false;
276     }
277 #endif
278
279 #ifdef ENABLE_TESTRECORDER
280     return myHandler ? myHandler->handle( receiver, e ) :
281       TestApplication::notify( receiver, e );
282 #else
283     return myHandler ? myHandler->handle( receiver, e ) :
284       QApplication::notify( receiver, e );
285 #endif
286   }
287   SUIT_ExceptionHandler* handler() const { return myHandler; }
288   void setHandler( SUIT_ExceptionHandler* h ) { myHandler = h; }
289
290 private:
291   SUIT_ExceptionHandler* myHandler;
292 };
293
294 // class which calls SALOME::Session::GetInterface() from another thread
295 // to avoid mutual lock ( if called from the same thread as main()
296 class GetInterfaceThread : public QThread
297 {
298 public:
299   GetInterfaceThread( SALOME::Session_var s ) : session ( s )
300   {
301     start();
302   }
303 protected:
304   virtual void run()
305   {
306     if ( !CORBA::is_nil( session ) )
307       session->GetInterface();
308     else
309       printf( "\nFATAL ERROR: SALOME::Session object is nil! Can not display GUI\n\n" );
310   }
311 private:
312   SALOME::Session_var session;
313 };
314
315 // returns true if 'str' is found in argv
316 bool isFound( const char* str, int argc, char** argv )
317 {
318   for ( int i = 1; i <= ( argc-1 ); i++ )
319     if ( !strcmp( argv[i], str ) )
320       return true;
321   return false;
322 }
323
324 void killOmniNames()
325 {
326   SALOME_LifeCycleCORBA::killOmniNames();
327 }
328
329 // shutdown standalone servers
330 void shutdownServers( SALOME_NamingService* theNS )
331 {
332   SALOME_LifeCycleCORBA lcc(theNS);
333   lcc.shutdownServers();
334 }
335
336 // ---------------------------- MAIN -----------------------
337 int main( int argc, char **argv )
338 {
339   // Install Qt debug messages handler
340   qInstallMsgHandler( MessageOutput );
341   
342   // Create Qt application instance;
343   // this should be done the very first!
344   SALOME_QApplication _qappl( argc, argv );
345
346   // Add application library path (to search style plugin etc...)
347   QString path = QDir::convertSeparators( SUIT_Tools::addSlash( QString( ::getenv( "GUI_ROOT_DIR" ) ) ) + QString( "bin/salome" ) );
348   _qappl.addLibraryPath( path );
349
350   bool isGUI    = isFound( "GUI",    argc, argv );
351   bool isSplash = isFound( "SPLASH", argc, argv );
352   // Show splash screen (only if both the "GUI" and "SPLASH" parameters are set)
353   // Note, that user preferences are not taken into account for splash settings -
354   // it is a property of the application!
355   QtxSplash* splash = 0;
356   if ( isGUI && isSplash ) {
357     // ...create resource manager
358     SUIT_ResourceMgr resMgr( "SalomeApp", QString( "%1Config" ) );
359     resMgr.setCurrentFormat( "xml" );
360     resMgr.setWorkingMode( QtxResourceMgr::IgnoreUserValues );
361     resMgr.loadLanguage( "LightApp", "en" );
362     //
363     splash = QtxSplash::splash( QPixmap() );
364     splash->readSettings( &resMgr );
365     if ( splash->pixmap().isNull() )
366       splash->setPixmap( resMgr.loadPixmap( "LightApp", QObject::tr( "ABOUT_SPLASH" ) ) );
367     if ( splash->pixmap().isNull() ) {
368       delete splash;
369       splash = 0;
370     }
371     else {
372       splash->setOption( "%A", QObject::tr( "APP_NAME" ) );
373       splash->setOption( "%V", QObject::tr( "ABOUT_VERSION" ).arg( salomeVersion() ) );
374       splash->setOption( "%L", QObject::tr( "ABOUT_LICENSE" ) );
375       splash->setOption( "%C", QObject::tr( "ABOUT_COPYRIGHT" ) );
376       splash->show();
377       QApplication::instance()->processEvents();
378     }
379   }
380
381   
382   // Initialization
383   int result = -1;
384
385   CORBA::ORB_var orb;
386   PortableServer::POA_var poa;
387
388   SUIT_Session* aGUISession = 0;
389   SALOME_NamingService* _NS = 0;
390   GetInterfaceThread* guiThread = 0;
391   Session_ServerLauncher* myServerLauncher = 0;
392
393   try {
394     // ...initialize Python (only once)
395     int   _argc   = 1;
396     char* _argv[] = {""};
397     KERNEL_PYTHON::init_python( _argc,_argv );
398     PyEval_RestoreThread( KERNEL_PYTHON::_gtstate );
399     if ( !KERNEL_PYTHON::salome_shared_modules_module ) // import only once
400       KERNEL_PYTHON::salome_shared_modules_module = PyImport_ImportModule( "salome_shared_modules" );
401     if ( !KERNEL_PYTHON::salome_shared_modules_module ) {
402       INFOS( "salome_shared_modules_module == NULL" );
403       PyErr_Print();
404     }
405     PyEval_ReleaseThread( KERNEL_PYTHON::_gtstate );
406
407     // ...create ORB, get RootPOA object, NamingService, etc.
408     ORB_INIT &init = *SINGLETON_<ORB_INIT>::Instance();
409     ASSERT( SINGLETON_<ORB_INIT>::IsAlreadyExisting() );
410     int orbArgc = 1;
411     orb = init( orbArgc, argv );
412
413     CORBA::Object_var obj = orb->resolve_initial_references( "RootPOA" );
414     poa = PortableServer::POA::_narrow( obj );
415
416     PortableServer::POAManager_var pman = poa->the_POAManager();
417     pman->activate() ;
418     MESSAGE( "pman->activate()" );
419
420     _NS = new SALOME_NamingService( orb );
421
422     result = 0;
423   }
424   catch ( SALOME_Exception& e ) {
425     INFOS( "run(): SALOME::SALOME_Exception is caught: "<<e.what() );
426   }
427   catch ( CORBA::SystemException& e ) {
428     INFOS( "Caught CORBA::SystemException." );
429   }
430   catch ( CORBA::Exception& e ) {
431     INFOS( "Caught CORBA::Exception." );
432     CORBA::Any tmp;
433     tmp<<= e;
434     CORBA::TypeCode_var tc = tmp.type();
435     const char *p = tc->name();
436     INFOS ( "run(): CORBA exception of the kind : "<<p<< " is caught" );
437   }
438   catch ( exception& e ) {
439     INFOS( "run(): An exception has been caught: " <<e.what() );
440   }
441   catch (...) {
442     INFOS( "Caught unknown exception." );
443   }
444
445   QMutex _GUIMutex, _SessionMutex, _SplashMutex;
446   QWaitCondition _ServerLaunch, _SessionStarted, _SplashStarted;
447
448   // lock session mutex to ensure that GetInterface is not called
449   // until all initialization is done
450   _SessionMutex.lock();
451
452   if ( !result ) {
453     // Start embedded servers launcher (Registry, SALOMEDS, etc.)
454     // ...lock mutex to block embedded servers launching thread until wait( mutex )
455     _GUIMutex.lock();  
456     // ...create launcher
457     myServerLauncher = new Session_ServerLauncher( argc, argv, orb, poa, &_GUIMutex, &_ServerLaunch, &_SessionMutex, &_SessionStarted );
458     // ...block this thread until launcher is ready
459     _ServerLaunch.wait( &_GUIMutex );
460     
461     // Start servers check thread (splash)
462     if ( splash ) {
463       // ...lock mutex to block splash thread until wait( mutex )
464       _SplashMutex.lock();
465       // ...create servers checking thread
466       Session_ServerCheck sc( &_SplashMutex, &_SplashStarted );
467       // ... set initial progress
468       splash->setProgress( 0, sc.totalSteps() );
469       // start check loop 
470       while ( true ) {
471         int step    = sc.currentStep();
472         int total   = sc.totalSteps();
473         QString msg = sc.currentMessage();
474         QString err = sc.error();
475         if ( !err.isEmpty() ) {
476           QtxSplash::setError( err );
477           QApplication::instance()->processEvents();
478           result = -1;
479           break;
480         }
481         QtxSplash::setStatus( msg, step );
482         QApplication::instance()->processEvents();
483         if ( step >= total )
484           break;
485         // ...block this thread until servers checking is finished
486         _SplashStarted.wait( &_SplashMutex );
487       }
488       // ...unlock mutex 'cause it is no more needed
489       _SplashMutex.unlock();
490     }
491
492     // Finalize embedded servers launcher 
493     // ...block this thread until launcher is finished
494     _ServerLaunch.wait( &_GUIMutex );
495     // ...unlock mutex 'cause it is no more needed
496     _GUIMutex.unlock();
497   }
498
499   bool shutdown = false;
500   if ( !result ) {
501     // Launch GUI activator
502     if ( isGUI ) {
503       if ( splash )
504         splash->setStatus( QApplication::translate( "", "Activating desktop..." ) );
505       // ...retrieve Session interface reference
506       CORBA::Object_var obj = _NS->Resolve( "/Kernel/Session" );
507       SALOME::Session_var session = SALOME::Session::_narrow( obj ) ;
508       ASSERT ( ! CORBA::is_nil( session ) );
509       // ...create GUI launcher
510       MESSAGE( "Session activated, Launch IAPP..." );
511       guiThread = new GetInterfaceThread( session );
512     }
513
514     // GUI activation
515     // Allow multiple activation/deactivation of GUI
516     while ( true ) {
517       MESSAGE( "waiting wakeAll()" );
518       _SessionStarted.wait( &_SessionMutex ); // to be reseased by Launch server thread when ready:
519       // atomic operation lock - unlock on mutex
520       // unlock mutex: serverThread runs, calls _ServerLaunch->wakeAll()
521       // this thread wakes up, and lock mutex
522
523       _SessionMutex.unlock();
524
525       // SUIT_Session creation
526       aGUISession = new SALOME_Session();
527
528       // Load SalomeApp dynamic library
529       MESSAGE( "creation SUIT_Application" );
530       SUIT_Application* aGUIApp = aGUISession->startApplication( "SalomeApp", 0, 0 );
531       if ( aGUIApp )
532       {
533         Style_Salome::initialize( aGUIApp->resourceMgr() );
534         if ( aGUIApp->resourceMgr()->booleanValue( "Style", "use_salome_style", true ) )
535           Style_Salome::apply();
536
537         if ( !isFound( "noexcepthandler", argc, argv ) )
538           _qappl.setHandler( aGUISession->handler() ); // after loading SalomeApp application
539                                                        // aGUISession contains SalomeApp_ExceptionHandler
540         // Run GUI loop
541         MESSAGE( "run(): starting the main event loop" );
542
543         if ( splash )
544           splash->finish( aGUIApp->desktop() );
545           
546         result = _qappl.exec();
547         
548         splash = 0;
549
550         if ( result == SUIT_Session::NORMAL ) { // desktop is closed by user from GUI
551           shutdown = aGUISession->exitFlags();
552           break;
553         }
554       }
555
556       delete aGUISession;
557       aGUISession = 0;
558
559       // Prepare _GUIMutex for a new GUI activation
560       _SessionMutex.lock();
561     }
562   }
563
564   // unlock Session mutex
565   _SessionMutex.unlock();
566   
567   if ( shutdown )
568     shutdownServers( _NS );
569
570   if ( myServerLauncher )
571     myServerLauncher->KillAll(); // kill embedded servers
572
573   delete aGUISession;
574   delete guiThread;
575   delete myServerLauncher;
576   delete _NS;
577
578   PyGILState_STATE gstate = PyGILState_Ensure();
579   Py_Finalize();
580
581   try  {
582     orb->shutdown(0);
583   }
584   catch (...) {
585     //////////////////////////////////////////////////////////////
586     // VSR: silently skip exception:
587     // CORBA.BAD_INV_ORDER.BAD_INV_ORDER_ORBHasShutdown 
588     // exception is raised when orb->destroy() is called and
589     // cpp continer is launched in the embedded mode
590     //////////////////////////////////////////////////////////////
591     // std::cerr << "Caught unexpected exception on destroy : ignored !!" << std::endl;
592   }
593
594   if ( shutdown )
595     killOmniNames();
596
597   return result;
598 }