libyui
3.0.10
|
00001 /* 00002 Copyright (C) 2000-2012 Novell, Inc 00003 This library is free software; you can redistribute it and/or modify 00004 it under the terms of the GNU Lesser General Public License as 00005 published by the Free Software Foundation; either version 2.1 of the 00006 License, or (at your option) version 3.0 of the License. This library 00007 is distributed in the hope that it will be useful, but WITHOUT ANY 00008 WARRANTY; without even the implied warranty of MERCHANTABILITY or 00009 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 00010 License for more details. You should have received a copy of the GNU 00011 Lesser General Public License along with this library; if not, write 00012 to the Free Software Foundation, Inc., 51 Franklin Street, Fifth 00013 Floor, Boston, MA 02110-1301 USA 00014 */ 00015 00016 00017 /*-/ 00018 00019 File: YUI.cc 00020 00021 Author: Stefan Hundhammer <sh@suse.de> 00022 00023 /-*/ 00024 00025 00026 #define VERBOSE_COMM 0 // VERY verbose thread communication logging 00027 00028 #include <stdio.h> 00029 #include <string.h> // strerror() 00030 #include <unistd.h> // pipe() 00031 #include <fcntl.h> // fcntl() 00032 #include <errno.h> 00033 #include <pthread.h> 00034 #include <stdlib.h> // getenv() 00035 00036 #include <stack> 00037 00038 #define YUILogComponent "ui" 00039 #include "YUILog.h" 00040 00041 #include "YUI.h" 00042 #include "YUILoader.h" 00043 #include "YUISymbols.h" 00044 #include "YDialog.h" 00045 #include "YApplication.h" 00046 #include "YMacro.h" 00047 #include "YButtonBox.h" 00048 #include "YEnvVar.h" 00049 #include "YBuiltinCaller.h" 00050 00051 using std::endl; 00052 00053 // Environment variable to determine button order 00054 // (set to "KDE" or "GNOME" - case insensitive) 00055 #define ENV_BUTTON_ORDER "Y2_BUTTON_ORDER" 00056 00057 // Keep dialog stack before the YUITerminator 00058 // so that it is destroyed afterwards. 00059 // YUITerminator deletes _yui which calls YUI::~YUI 00060 // which accesses the dialog stack to remove all dialogs 00061 std::stack<YDialog *> YDialog::_dialogStack; 00062 YUI * YUI::_ui = 0; 00063 00064 static bool uiDeleted = false; 00065 00066 extern void * start_ui_thread( void * yui ); 00067 00068 00069 YUI::YUI( bool withThreads ) 00070 : _withThreads( withThreads ) 00071 , _uiThread( 0 ) 00072 , _builtinCaller( 0 ) 00073 , _terminate_ui_thread( false ) 00074 , _eventsBlocked( false ) 00075 { 00076 yuiMilestone() << "This is libyui " << VERSION << std::endl; 00077 yuiMilestone() << "Creating UI " << ( withThreads ? "with" : "without" ) << " threads" << endl; 00078 _ui = this; 00079 } 00080 00081 00082 YUI::~YUI() 00083 { 00084 if ( _ui ) 00085 { 00086 if ( _withThreads && _uiThread ) 00087 { 00088 yuiError() << "shutdownThreads() was never called!" << endl; 00089 yuiError() << "shutting down now - this might segfault" << endl; 00090 shutdownThreads(); 00091 } 00092 00093 if ( YDialog::openDialogsCount() > 0 ) 00094 yuiError() << YDialog::openDialogsCount() << " open dialogs left over" << endl; 00095 00096 if ( _builtinCaller ) 00097 delete _builtinCaller; 00098 00099 YDialog::deleteAllDialogs(); 00100 00101 YMacro::deleteRecorder(); 00102 YMacro::deletePlayer(); 00103 00104 _ui = 0; 00105 uiDeleted = true; 00106 } 00107 } 00108 00109 00110 void 00111 YUI::uiThreadDestructor() 00112 { 00113 YDialog::deleteAllDialogs(); 00114 } 00115 00116 00117 YUI * 00118 YUI::ui() 00119 { 00120 ensureUICreated(); 00121 return _ui; 00122 } 00123 00124 00125 YWidgetFactory * 00126 YUI::widgetFactory() 00127 { 00128 static YWidgetFactory * factory = 0; 00129 00130 ensureUICreated(); 00131 00132 if ( ! factory ) 00133 factory = ui()->createWidgetFactory(); 00134 00135 YUI_CHECK_PTR( factory ); 00136 return factory; 00137 } 00138 00139 00140 YOptionalWidgetFactory * 00141 YUI::optionalWidgetFactory() 00142 { 00143 static YOptionalWidgetFactory * factory = 0; 00144 00145 ensureUICreated(); 00146 00147 if ( ! factory ) 00148 factory = ui()->createOptionalWidgetFactory(); 00149 00150 YUI_CHECK_PTR( factory ); 00151 return factory; 00152 } 00153 00154 00155 YApplication * 00156 YUI::app() 00157 { 00158 static YApplication * app = 0; 00159 00160 ensureUICreated(); 00161 00162 if ( ! app ) 00163 app = ui()->createApplication(); 00164 00165 YUI_CHECK_PTR( app ); 00166 return app; 00167 } 00168 00169 00170 void YUI::ensureUICreated() 00171 { 00172 if ( _ui ) 00173 return; 00174 00175 if ( uiDeleted ) 00176 YUI_THROW( YUIException( "UI already deleted" ) ); 00177 00178 YUILoader::loadUI(); 00179 } 00180 00181 00182 void YUI::topmostConstructorHasFinished() 00183 { 00184 // The ui thread must not be started before the constructor 00185 // of the actual user interface is finished. Otherwise there 00186 // is a race condition. The ui thread would go into idleLoop() 00187 // before the ui is really setup. For example the Qt interface 00188 // does a processOneEvent in the idleLoop(). This may do a 00189 // repaint operation on the dialog that is just under construction! 00190 // 00191 // Therefore the creation of the thread is delayed and performed in this 00192 // method. It must be called at the end of the constructor of the specific 00193 // UI (the Qt UI or the NCurses UI). 00194 00195 if ( _withThreads ) 00196 { 00197 if ( pipe( pipe_from_ui ) == 0 && 00198 pipe( pipe_to_ui ) == 0 ) 00199 { 00200 00201 // Make fd non blockable the ui thread reads from 00202 long arg; 00203 arg = fcntl( pipe_to_ui[0], F_GETFL ); 00204 if ( fcntl( pipe_to_ui[0], F_SETFL, arg | O_NONBLOCK ) < 0 ) 00205 { 00206 yuiError() << "Couldn't set O_NONBLOCK: errno: " << errno << " " << strerror( errno ) << endl; 00207 _withThreads = false; 00208 close( pipe_to_ui[0] ); 00209 close( pipe_to_ui[1] ); 00210 close( pipe_from_ui[0] ); 00211 close( pipe_from_ui[1] ); 00212 } 00213 else 00214 { 00215 #if VERBOSE_COMM 00216 yuiDebug() << "Inter-thread communication pipes set up" << endl; 00217 #endif 00218 _terminate_ui_thread = false; 00219 createUIThread(); 00220 } 00221 } 00222 else 00223 { 00224 yuiError() << "pipe() failed: errno: " << errno << " " << strerror( errno ) << endl; 00225 exit(2); 00226 } 00227 } 00228 else 00229 { 00230 yuiMilestone() << "Running without threads" << endl; 00231 } 00232 } 00233 00234 00235 void YUI::createUIThread() 00236 { 00237 pthread_attr_t attr; 00238 pthread_attr_init( & attr ); 00239 int ret = pthread_create( & _uiThread, & attr, start_ui_thread, this ); 00240 00241 if ( ret != 0 ) 00242 yuiError() << "pthread_create() failed: " << errno << " " << strerror( errno ) << endl; 00243 } 00244 00245 00246 void YUI::terminateUIThread() 00247 { 00248 yuiDebug() << "Sending shutdown message to UI thread" << endl; 00249 00250 _terminate_ui_thread = true; 00251 signalUIThread(); 00252 waitForUIThread(); 00253 pthread_join( _uiThread, 0 ); 00254 00255 yuiDebug() << "UI thread shut down correctly" << endl; 00256 } 00257 00258 00259 void YUI::shutdownThreads() 00260 { 00261 if ( _uiThread ) 00262 { 00263 terminateUIThread(); 00264 _uiThread = 0; 00265 close( pipe_to_ui[0] ); 00266 close( pipe_to_ui[1] ); 00267 close( pipe_from_ui[0] ); 00268 close( pipe_from_ui[1] ); 00269 } 00270 } 00271 00272 00273 void YUI::signalUIThread() 00274 { 00275 static char arbitrary = 42; 00276 if ( write ( pipe_to_ui[1], & arbitrary, 1 ) == -1 ) 00277 yuiError() << "Writing byte to UI thread failed" << endl; 00278 00279 #if VERBOSE_COMM 00280 yuiDebug() << "Wrote byte to UI thread" << endl; 00281 #endif 00282 } 00283 00284 00285 bool YUI::waitForUIThread() 00286 { 00287 char arbitrary; 00288 int result; 00289 00290 do { 00291 #if VERBOSE_COMM 00292 yuiDebug() << "Waiting for ui thread..." << endl; 00293 #endif 00294 result = read( pipe_from_ui[0], & arbitrary, 1 ); 00295 if ( result == -1 ) 00296 { 00297 if ( errno == EINTR || errno == EAGAIN ) 00298 continue; 00299 else 00300 yuiError() << "waitForUIThread: errno: " << errno << " " << strerror( errno ) << endl; 00301 } 00302 } while ( result == 0 ); 00303 00304 #if VERBOSE_COMM 00305 yuiDebug() << "Read byte from ui thread" << endl; 00306 #endif 00307 00308 // return true if we really did get a signal byte 00309 return result != -1; 00310 } 00311 00312 00313 void YUI::signalYCPThread() 00314 { 00315 static char arbitrary; 00316 if ( write( pipe_from_ui[1], & arbitrary, 1 ) == -1 ) 00317 yuiError() << "Writing byte to YCP thread failed" << endl; 00318 00319 #if VERBOSE_COMM 00320 yuiDebug() << "Wrote byte to YCP thread" << endl; 00321 #endif 00322 } 00323 00324 00325 bool YUI::waitForYCPThread() 00326 { 00327 char arbitrary; 00328 00329 int result; 00330 do { 00331 #if VERBOSE_COMM 00332 yuiDebug() << "Waiting for YCP thread..." << endl; 00333 #endif 00334 result = read( pipe_to_ui[0], & arbitrary, 1 ); 00335 if ( result == -1 ) 00336 { 00337 if ( errno == EINTR || errno == EAGAIN ) 00338 continue; 00339 else 00340 yuiError() << "waitForYCPThread: errno: " << errno << " " << strerror( errno ) << endl; 00341 } 00342 } while ( result == 0 ); 00343 00344 #if VERBOSE_COMM 00345 yuiDebug() << "Read byte from YCP thread" << endl; 00346 #endif 00347 00348 // return true if we really did get a signal byte 00349 return result != -1; 00350 } 00351 00352 00353 void YUI::uiThreadMainLoop() 00354 { 00355 while ( true ) 00356 { 00357 idleLoop( pipe_to_ui[0] ); 00358 00359 // The pipe is non-blocking, so we have to check if we really read a 00360 // signal byte. Although idleLoop already does a select(), this seems to 00361 // be necessary. Anyway: Why do we set the pipe to non-blocking if we 00362 // wait in idleLoop for it to become readable? It is needed in 00363 // YUIQt::idleLoop for QSocketNotifier. 00364 00365 if ( ! waitForYCPThread () ) 00366 continue; 00367 00368 if ( _terminate_ui_thread ) 00369 { 00370 uiThreadDestructor(); 00371 signalYCPThread(); 00372 yuiDebug() << "Shutting down UI main loop" << endl; 00373 return; 00374 } 00375 00376 if ( _builtinCaller ) 00377 _builtinCaller->call(); 00378 else 00379 yuiError() << "No builtinCaller set" << endl; 00380 00381 signalYCPThread(); 00382 } 00383 } 00384 00385 00386 void YUI::setButtonOrderFromEnvironment() 00387 { 00388 YButtonOrder buttonOrder = YButtonBox::layoutPolicy().buttonOrder; 00389 YButtonOrder oldButtonOrder = buttonOrder; 00390 00391 YEnvVar lastEnv; 00392 00393 // 00394 // $DESKTOP_SESSION 00395 // 00396 00397 YEnvVar env( "DESKTOP_SESSION" ); 00398 yuiDebug() << env << endl; 00399 00400 if ( env == "kde" || 00401 env == "xfce" ) 00402 { 00403 buttonOrder = YKDEButtonOrder; 00404 lastEnv = env; 00405 } 00406 else if ( env == "gnome" ) 00407 { 00408 buttonOrder = YGnomeButtonOrder; 00409 lastEnv = env; 00410 } 00411 00412 // 00413 // $WINDOWMANAGER 00414 // 00415 00416 env = YEnvVar( "WINDOWMANAGER" ); 00417 yuiDebug() << env << endl; 00418 00419 if ( env.contains( "gnome" ) ) 00420 { 00421 buttonOrder = YGnomeButtonOrder; 00422 lastEnv = env; 00423 } 00424 else if ( env.contains( "kde" ) ) 00425 { 00426 buttonOrder = YKDEButtonOrder; 00427 lastEnv = env; 00428 } 00429 00430 00431 // 00432 // $Y2_BUTTON_ORDER 00433 // 00434 00435 env = YEnvVar( ENV_BUTTON_ORDER ); 00436 yuiDebug() << env << endl; 00437 00438 if ( env == "gnome" ) 00439 { 00440 buttonOrder = YGnomeButtonOrder; 00441 lastEnv = env; 00442 } 00443 else if ( env == "kde" ) 00444 { 00445 buttonOrder = YKDEButtonOrder; 00446 lastEnv = env; 00447 } 00448 else if ( ! env.value().empty() ) 00449 { 00450 yuiWarning() << "Ignoring unknown value of " << env << endl; 00451 } 00452 00453 00454 if ( buttonOrder != oldButtonOrder ) 00455 { 00456 std::string buttonOrderStr; 00457 00458 switch ( buttonOrder ) 00459 { 00460 case YKDEButtonOrder: 00461 buttonOrderStr = "KDE"; 00462 YButtonBox::setLayoutPolicy( YButtonBox::kdeLayoutPolicy() ); 00463 break; 00464 00465 case YGnomeButtonOrder: 00466 buttonOrderStr = "GNOME"; 00467 YButtonBox::setLayoutPolicy( YButtonBox::gnomeLayoutPolicy() ); 00468 break; 00469 00470 // Intentionally omitting "default" branch so GCC can catch unhandled enums 00471 } 00472 00473 yuiMilestone() << "Switching to " << buttonOrderStr 00474 << " button order because of " << lastEnv 00475 << endl; 00476 } 00477 } 00478 00479 00480 // 00481 // ---------------------------------------------------------------------- 00482 // 00483 00484 void * start_ui_thread( void * yui ) 00485 { 00486 YUI * ui = (YUI *) yui; 00487 00488 #if VERBOSE_COMM 00489 yuiDebug() << "Starting UI thread" << endl; 00490 #endif 00491 00492 if ( ui ) 00493 ui->uiThreadMainLoop(); 00494 return 0; 00495 } 00496 00497 00498 // 00499 // ---------------------------------------------------------------------- 00500 // 00501 00502 00503 /** 00504 * Helper class to make sure the UI is properly shut down. 00505 **/ 00506 class YUITerminator 00507 { 00508 public: 00509 YUITerminator() {} 00510 00511 /** 00512 * Destructor. 00513 * 00514 * If there still is a UI, it will be deleted. 00515 * If there is none, this will do nothing. 00516 **/ 00517 ~YUITerminator(); 00518 }; 00519 00520 00521 YUITerminator::~YUITerminator() 00522 { 00523 if ( YUI::_ui ) 00524 { 00525 yuiMilestone() << "Shutting down UI" << endl; 00526 delete YUI::_ui; 00527 00528 YUI::_ui = 0; 00529 } 00530 } 00531 00532 00533 /** 00534 * Static YUITerminator instance: It will make sure the UI is deleted in its 00535 * global destructor. If the UI is already destroyed, it will do nothing. If 00536 * there still is a UI object, it will be deleted. 00537 * 00538 * This is particularly important for the NCurses UI so the terminal settings 00539 * are properly restored. 00540 **/ 00541 static YUITerminator uiTerminator; 00542 00543 00544 00545 // EOF