kcmoduleproxy.cpp
00001 /* This file is part of the KDE project 00002 Copyright (C) 2004 Frans Englich <frans.englich@telia.com> 00003 Copyright (C) 2003 Matthias Kretz <kretz@kde.org> 00004 00005 This library is free software; you can redistribute it and/or 00006 modify it under the terms of the GNU Library General Public 00007 License version 2 as published by the Free Software Foundation. 00008 00009 This library is distributed in the hope that it will be useful, 00010 but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 Library General Public License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to 00016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 00017 Boston, MA 02110-1301, USA. 00018 */ 00019 00020 #include <tqapplication.h> 00021 #include <tqcursor.h> 00022 #include <tqdatastream.h> 00023 #include <tqevent.h> 00024 #include <tqfileinfo.h> 00025 #include <tqframe.h> 00026 #include <tqlabel.h> 00027 #include <tqlayout.h> 00028 #include <tqpoint.h> 00029 #include <tqscrollview.h> 00030 #include <tqtextstream.h> 00031 #include <tqvbox.h> 00032 #include <tqwhatsthis.h> 00033 #include <tqwidget.h> 00034 00035 #include <dcopclient.h> 00036 #include <qxembed.h> 00037 00038 #include <kapplication.h> 00039 #include <kaboutdata.h> 00040 #include <kcmodule.h> 00041 #include <kcmoduleinfo.h> 00042 #include <kcmoduleloader.h> 00043 #include <kdebug.h> 00044 #include <kdialog.h> 00045 #include <klocale.h> 00046 #include <kprocess.h> 00047 #include <kservice.h> 00048 #include <kstandarddirs.h> 00049 #include <kuser.h> 00050 00051 #include <X11/Xlib.h> 00052 00053 #include "kcmoduleproxy.h" 00054 #include "kcmoduleproxyIface.h" 00055 #include "kcmoduleproxyIfaceImpl.h" 00056 00057 /***************************************************************/ 00058 class KCModuleProxy::KCModuleProxyPrivate 00059 { 00060 public: 00061 KCModuleProxyPrivate( const KCModuleInfo & info ) 00062 : args( 0 ) 00063 , kcm( 0 ) 00064 //, view( 0 ) 00065 , embedWidget( 0 ) 00066 , rootProcess ( 0 ) 00067 , embedFrame ( 0 ) 00068 , dcopObject( 0 ) 00069 , dcopClient( 0 ) 00070 , topLayout( 0 ) 00071 , rootCommunicator( 0 ) 00072 , rootInfo( 0 ) 00073 , modInfo( info ) 00074 , withFallback( false ) 00075 , changed( false ) 00076 , rootMode( false ) 00077 , bogusOccupier( false ) 00078 , isInitialized( false ) 00079 {} 00080 00081 ~KCModuleProxyPrivate() 00082 { 00083 delete rootInfo; // Delete before embedWidget! 00084 delete embedWidget; // Delete before embedFrame! 00085 delete embedFrame; 00086 delete dcopClient; 00087 delete dcopObject; 00088 delete rootCommunicator; 00089 delete rootProcess; 00090 delete kcm; 00091 } 00092 00093 TQStringList args; 00094 KCModule *kcm; 00095 QXEmbed *embedWidget; 00096 KProcess *rootProcess; 00097 TQVBox *embedFrame; 00098 KCModuleProxyIfaceImpl *dcopObject; 00099 DCOPClient *dcopClient; 00100 TQVBoxLayout *topLayout; /* Contains TQScrollView view, and root stuff */ 00101 KCModuleProxyRootCommunicatorImpl *rootCommunicator; 00102 TQLabel *rootInfo; 00103 TQCString dcopName; 00104 KCModuleInfo modInfo; 00105 bool withFallback; 00106 bool changed; 00107 bool rootMode; 00108 bool bogusOccupier; 00109 bool isInitialized; 00110 }; 00111 /***************************************************************/ 00112 00113 00114 00115 /* 00116 TODO: 00117 00118 - How KCModuleProxy behaves wrt memory leaks and behavior, when exiting 00119 from root mode is not tested, because no code make use of it. It needs 00120 work, if it should be used. 00121 00122 - Should write a document which outlines test cases, to avoid 00123 regressions. This class is a hazard. 00124 00125 - Two Layout problems in runAsRoot: 00126 * lblBusy doesn't show 00127 * d->kcm/d->rootInfo doesn't get it right when the user 00128 presses cancel in the kdesu dialog 00129 00130 - Resizing horizontally is contrained; minimum size is set somewhere. 00131 It appears to be somehow derived from the module's size. 00132 00133 - Prettify: set icon in KCMultiDialog. 00134 00135 - Perhaps it's possible to link against kdesu such that 00136 the dialog is in process? 00137 00138 */ 00139 /***************************************************************/ 00140 KCModule * KCModuleProxy::realModule() const 00141 { 00142 00143 /* 00144 * Note, don't call any function that calls realModule() since 00145 * that leads to an infinite loop. 00146 */ 00147 00148 kdDebug(711) << k_funcinfo << endl; 00149 00150 /* Already loaded */ 00151 if( d->kcm ) 00152 return d->kcm; 00153 00154 /* /We/ have no kcm, but kcmshell running with root prevs does.. */ 00155 if( d->rootMode ) 00156 return 0; 00157 00158 TQApplication::setOverrideCursor( Qt::WaitCursor ); 00159 00160 KCModuleProxy * that = const_cast<KCModuleProxy*>( this ); 00161 00162 if( !d->isInitialized ) 00163 { 00164 d->dcopName = TQString(moduleInfo().handle().prepend("KCModuleProxy-")).utf8(); 00165 d->topLayout = new TQVBoxLayout( that, 0, 0, "topLayout" ); 00166 00167 d->isInitialized = true; 00168 } 00169 00170 if( !d->dcopClient ) 00171 d->dcopClient = new DCOPClient(); 00172 00173 if( !d->dcopClient->isRegistered() ) 00174 d->dcopClient->registerAs( d->dcopName, false ); 00175 00176 d->dcopClient->setAcceptCalls( true ); 00177 00178 if( d->dcopClient->appId() == d->dcopName || d->bogusOccupier ) 00179 { /* We got the name we requested, because no one was before us, 00180 * or, it was an random application which had picked that name */ 00181 kdDebug(711) << "Module not already loaded, loading module" << endl; 00182 00183 d->dcopObject = new KCModuleProxyIfaceImpl( d->dcopName, that ); 00184 00185 d->kcm = KCModuleLoader::loadModule( moduleInfo(), KCModuleLoader::Inline, d->withFallback, 00186 that, name(), d->args ); 00187 00188 connect( d->kcm, TQT_SIGNAL( changed( bool ) ), 00189 TQT_SLOT(moduleChanged(bool)) ); 00190 connect( d->kcm, TQT_SIGNAL( destroyed() ), 00191 TQT_SLOT( moduleDestroyed() ) ); 00192 connect( d->kcm, TQT_SIGNAL(quickHelpChanged()), 00193 TQT_SIGNAL(quickHelpChanged())); 00194 TQWhatsThis::add( that, d->kcm->quickHelp() ); 00195 00196 d->topLayout->addWidget( d->kcm ); 00197 00198 if ( !d->rootInfo && /* If it's already done */ 00199 moduleInfo().needsRootPrivileges() /* root, anyone? */ && 00200 !KUser().isSuperUser() ) /* Not necessary if we're root */ 00201 { 00202 00203 d->rootInfo = new TQLabel( that, "rootInfo" ); 00204 d->topLayout->insertWidget( 0, d->rootInfo ); 00205 00206 d->rootInfo->setFrameShape(TQFrame::Box); 00207 d->rootInfo->setFrameShadow(TQFrame::Raised); 00208 00209 const TQString msg = d->kcm->rootOnlyMsg(); 00210 if( msg.isEmpty() ) 00211 d->rootInfo->setText(i18n( 00212 "<b>Changes in this section requires root access.</b><br />" 00213 "Click the \"Administrator Mode\" button to " 00214 "allow modifications.")); 00215 else 00216 d->rootInfo->setText(msg); 00217 00218 TQWhatsThis::add( d->rootInfo, i18n( 00219 "This section requires special permissions, probably " 00220 "for system-wide changes; therefore, it is " 00221 "required that you provide the root password to be " 00222 "able to change the module's properties. If " 00223 "you do not provide the password, the module will be " 00224 "disabled.")); 00225 } 00226 } 00227 else 00228 { 00229 kdDebug(711) << "Module already loaded, loading KCMError" << endl; 00230 00231 d->dcopClient->detach(); 00232 /* Re-register as anonymous */ 00233 d->dcopClient->attach(); 00234 00235 d->dcopClient->setNotifications( true ); 00236 connect( d->dcopClient, TQT_SIGNAL( applicationRemoved( const TQCString& )), 00237 TQT_SLOT( applicationRemoved( const TQCString& ))); 00238 00239 /* Figure out the name of where the module is already loaded */ 00240 TQByteArray replyData, data; 00241 TQCString replyType; 00242 TQString result; 00243 TQDataStream arg, stream( replyData, IO_ReadOnly ); 00244 00245 if( d->dcopClient->call( d->dcopName, d->dcopName, "applicationName()", 00246 data, replyType, replyData )) 00247 { 00248 stream >> result; 00249 00250 d->kcm = KCModuleLoader::reportError( KCModuleLoader::Inline, 00251 i18n( "Argument is application name", "This configuration section is " 00252 "already opened in %1" ).arg( result ), " ", that ); 00253 00254 d->topLayout->addWidget( d->kcm ); 00255 } 00256 else 00257 { 00258 kdDebug(711) << "Calling KCModuleProxy's DCOP interface for fetching the name failed." << endl; 00259 d->bogusOccupier = true; 00260 TQApplication::restoreOverrideCursor(); 00261 return realModule(); 00262 } 00263 } 00264 00265 TQApplication::restoreOverrideCursor(); 00266 00267 return d->kcm; 00268 } 00269 00270 void KCModuleProxy::applicationRemoved( const TQCString& app ) 00271 { 00272 if( app == d->dcopName ) 00273 { 00274 /* Violence: Get rid of KCMError & CO, so that 00275 * realModule() attempts to reload the module */ 00276 delete d->kcm; 00277 d->kcm = 0; 00278 d->dcopClient->setNotifications( false ); 00279 realModule(); 00280 d->kcm->show(); 00281 } 00282 } 00283 00284 void KCModuleProxy::showEvent( TQShowEvent * ev ) 00285 { 00286 00287 kdDebug(711) << k_funcinfo << endl; 00288 ( void )realModule(); 00289 00290 /* We have no kcm, if we're in root mode */ 00291 if( d->kcm ) 00292 d->kcm->show(); 00293 00294 TQWidget::showEvent( ev ); 00295 00296 } 00297 00298 void KCModuleProxy::runAsRoot() 00299 { 00300 if ( !moduleInfo().needsRootPrivileges() ) 00301 return; 00302 00303 TQApplication::setOverrideCursor( Qt::WaitCursor ); 00304 00305 delete d->rootProcess; 00306 delete d->embedWidget; 00307 delete d->embedFrame; 00308 00309 d->embedFrame = new TQVBox( this, "embedFrame" ); 00310 d->embedFrame->setFrameStyle( TQFrame::Box | TQFrame::Raised ); 00311 00312 TQPalette pal( red ); 00313 pal.setColor( TQColorGroup::Background, 00314 colorGroup().background() ); 00315 d->embedFrame->setPalette( pal ); 00316 d->embedFrame->setLineWidth( 2 ); 00317 d->embedFrame->setMidLineWidth( 2 ); 00318 d->topLayout->addWidget(d->embedFrame,1); 00319 00320 d->embedWidget = new QXEmbed( d->embedFrame, "embedWidget" ); 00321 00322 d->embedFrame->show(); 00323 00324 TQLabel *lblBusy = new TQLabel(i18n("<big>Loading...</big>"), d->embedWidget, "lblBusy" ); 00325 lblBusy->setTextFormat(RichText); 00326 lblBusy->setAlignment(AlignCenter); 00327 lblBusy->setGeometry(0,0, d->kcm->width(), d->kcm->height()); 00328 lblBusy->show(); 00329 00330 deleteClient(); 00331 /* The DCOP registration is now gone, and it will occur again when kcmshell soon 00332 * registers. Here's a race condition in other words, but how likely is that? 00333 * 00334 * - It's a user initiated action, which means the user have to do weird stuff, very 00335 * quick. 00336 * - If the user _do_ manage to fsck up, the code will recover gracefully, see realModule(). 00337 * 00338 * So no worry. At the end of this function, communication with 00339 * the DCOP object is established. 00340 */ 00341 00342 /* Prepare the process to run the kcmshell */ 00343 TQString cmd = moduleInfo().service()->exec().stripWhiteSpace(); 00344 if (cmd.left(5) == "kdesu") 00345 { 00346 cmd = TQString(cmd.remove(0,5)).stripWhiteSpace(); 00347 00348 /* Remove all kdesu switches */ 00349 while( cmd.length() > 1 && cmd[ 0 ] == '-' ) 00350 cmd = TQString(cmd.remove( 0, cmd.find( ' ' ) )).stripWhiteSpace(); 00351 } 00352 00353 if (cmd.left(8) == "kcmshell") 00354 cmd = TQString(cmd.remove(0,8)).stripWhiteSpace(); 00355 00356 /* Run the process */ 00357 TQString kdesu = KStandardDirs::findExe("kdesu"); 00358 if (!kdesu.isEmpty()) 00359 { 00360 00361 d->rootProcess = new KProcess; 00362 00363 *d->rootProcess << kdesu; 00364 *d->rootProcess << "--nonewdcop" << "-n" << "-d" << TQString( "-i%1" ).arg(moduleInfo().icon()); 00365 00366 *d->rootProcess << TQString("%1 %2 --embed-proxy %3 --lang %4").arg(locate("exe", "kcmshell")) 00367 .arg(cmd).arg(d->embedWidget->winId()).arg(KGlobal::locale()->language()); 00368 00369 connect(d->rootProcess, TQT_SIGNAL(processExited(KProcess*)), TQT_SLOT(rootExited())); 00370 00371 if ( !d->rootProcess->start( KProcess::NotifyOnExit )) 00372 { 00373 d->rootMode = false; 00374 rootExited(); 00375 } 00376 else 00377 { 00378 d->rootMode = true; 00379 kapp->dcopClient(); 00380 d->rootCommunicator = new KCModuleProxyRootCommunicatorImpl( d->dcopName + "-RootCommunicator", this ); 00381 } 00382 00383 delete lblBusy; 00384 TQApplication::restoreOverrideCursor(); 00385 return; 00386 } 00387 00388 /* Clean up in case of failure */ 00389 delete d->embedWidget; 00390 d->embedWidget = 0; 00391 delete d->embedFrame; 00392 d->embedFrame = 0; 00393 00394 TQApplication::restoreOverrideCursor(); 00395 } 00396 00397 void KCModuleProxy::rootExited() 00398 { 00399 kdDebug(711) << k_funcinfo << endl; 00400 00401 if ( d->embedWidget->embeddedWinId() ) 00402 XDestroyWindow(qt_xdisplay(), d->embedWidget->embeddedWinId()); 00403 00404 delete d->embedWidget; 00405 d->embedWidget = 0; 00406 00407 delete d->rootProcess; 00408 d->rootProcess = 0; 00409 00410 delete d->embedFrame; 00411 d->embedFrame=0; 00412 00413 delete d->rootCommunicator; 00414 d->rootCommunicator = 0; 00415 00416 /* Such that the "ordinary" module loads again */ 00417 d->rootMode = false; 00418 00419 d->topLayout->invalidate(); 00420 00421 TQShowEvent ev; 00422 showEvent( &ev ); 00423 00424 moduleChanged( false ); 00425 emit childClosed(); 00426 } 00427 00428 KCModuleProxy::~KCModuleProxy() 00429 { 00430 deleteClient(); 00431 KCModuleLoader::unloadModule(moduleInfo()); 00432 00433 delete d; 00434 } 00435 00436 void KCModuleProxy::deleteClient() 00437 { 00438 if( d->embedWidget ) 00439 XKillClient(qt_xdisplay(), d->embedWidget->embeddedWinId()); 00440 00441 00442 delete d->kcm; 00443 d->kcm = 0; 00444 00445 delete d->dcopObject; 00446 d->dcopObject = 0; 00447 00448 if( d->dcopClient && !d->dcopClient->detach() ) 00449 kdDebug(711) << "Unregistering from DCOP failed." << endl; 00450 00451 delete d->dcopClient; 00452 d->dcopClient = 0; 00453 00454 kapp->syncX(); 00455 00456 } 00457 00458 void KCModuleProxy::moduleChanged( bool c ) 00459 { 00460 if( d->changed == c ) 00461 return; 00462 00463 d->changed = c; 00464 emit changed( c ); 00465 emit changed( this ); 00466 } 00467 00468 void KCModuleProxy::moduleDestroyed() 00469 { 00470 d->kcm = 0; 00471 } 00472 00473 KCModuleProxy::KCModuleProxy( const KService::Ptr & service, bool withFallback, 00474 TQWidget * parent, const char * name, const TQStringList & args) 00475 : TQWidget( parent, name ) 00476 { 00477 init( KCModuleInfo( service )); 00478 d->args = args; 00479 d->withFallback = withFallback; 00480 } 00481 00482 KCModuleProxy::KCModuleProxy( const KCModuleInfo & info, bool withFallback, 00483 TQWidget * parent, const char * name, const TQStringList & args ) 00484 : TQWidget( parent, name ) 00485 { 00486 init( info ); 00487 d->args = args; 00488 d->withFallback = withFallback; 00489 } 00490 00491 KCModuleProxy::KCModuleProxy( const TQString& serviceName, bool withFallback, 00492 TQWidget * parent, const char * name, 00493 const TQStringList & args) 00494 : TQWidget( parent, name ) 00495 { 00496 init( KCModuleInfo( serviceName )); 00497 d->args = args; 00498 d->withFallback = withFallback; 00499 } 00500 00501 void KCModuleProxy::init( const KCModuleInfo& info ) 00502 { 00503 kdDebug(711) << k_funcinfo << endl; 00504 00505 d = new KCModuleProxyPrivate( info ); 00506 00507 /* This is all we do for now; all the heavy work is 00508 * done in realModule(). It's called when the module 00509 * _actually_ is needed, in for example showEvent(). 00510 * The module is loaded "on demand" -- lazy loading. 00511 */ 00512 00513 } 00514 00515 void KCModuleProxy::load() 00516 { 00517 00518 if( d->rootMode ) 00519 callRootModule( "load()" ); 00520 else if( realModule() ) 00521 { 00522 d->kcm->load(); 00523 moduleChanged( false ); 00524 } 00525 } 00526 00527 void KCModuleProxy::save() 00528 { 00529 if( d->rootMode ) 00530 callRootModule( "save()" ); 00531 else if( d->changed && realModule() ) 00532 { 00533 d->kcm->save(); 00534 moduleChanged( false ); 00535 } 00536 } 00537 00538 void KCModuleProxy::callRootModule( const TQCString& function ) 00539 { 00540 TQByteArray sendData, replyData; 00541 TQCString replyType; 00542 00543 /* Note, we don't use d->dcopClient here, because it's used for 00544 * the loaded module(and it's not "us" when this function is called) */ 00545 if( !kapp->dcopClient()->call( d->dcopName, d->dcopName, function, sendData, 00546 replyType, replyData, true, -1 )) 00547 kdDebug(711) << "Calling function '" << function << "' failed." << endl; 00548 00549 } 00550 00551 void KCModuleProxy::defaults() 00552 { 00553 if( d->rootMode ) 00554 callRootModule( "defaults()" ); 00555 if( realModule() ) 00556 d->kcm->defaults(); 00557 } 00558 00559 TQString KCModuleProxy::quickHelp() const 00560 { 00561 00562 if( !d->rootMode ) 00563 return realModule() ? realModule()->quickHelp() : TQString::null; 00564 else 00565 { 00566 TQByteArray data, replyData; 00567 TQCString replyType; 00568 00569 if (kapp->dcopClient()->call(d->dcopName, d->dcopName, "quickHelp()", 00570 data, replyType, replyData)) 00571 kdDebug(711) << "Calling DCOP function bool changed() failed." << endl; 00572 else 00573 { 00574 TQDataStream reply(replyData, IO_ReadOnly); 00575 if (replyType == "TQString") 00576 { 00577 TQString result; 00578 reply >> result; 00579 return result; 00580 } 00581 else 00582 kdDebug(711) << "DCOP function changed() returned mumbo jumbo." << endl; 00583 } 00584 return TQString::null; 00585 } 00586 } 00587 00588 const KAboutData * KCModuleProxy::aboutData() const 00589 { 00590 if( !d->rootMode ) 00591 return realModule() ? realModule()->aboutData() : 0; 00592 else 00593 /* This needs fixing, perhaps cache a KAboutData copy 00594 * while in root mode? */ 00595 return 0; 00596 00597 00598 } 00599 00600 int KCModuleProxy::buttons() const 00601 { 00602 return realModule() ? realModule()->buttons() : 00603 KCModule::Help | KCModule::Default | KCModule::Apply ; 00604 } 00605 00606 TQString KCModuleProxy::rootOnlyMsg() const 00607 { 00608 return realModule() ? realModule()->rootOnlyMsg() : TQString::null; 00609 } 00610 00611 bool KCModuleProxy::useRootOnlyMsg() const 00612 { 00613 return realModule() ? realModule()->useRootOnlyMsg() : true; 00614 } 00615 00616 KInstance * KCModuleProxy::instance() const 00617 { 00618 return realModule() ? realModule()->instance() : 0; 00619 } 00620 00621 bool KCModuleProxy::changed() const 00622 { 00623 return d->changed; 00624 } 00625 00626 const KCModuleInfo& KCModuleProxy::moduleInfo() const 00627 { 00628 return d->modInfo; 00629 } 00630 00631 bool KCModuleProxy::rootMode() const 00632 { 00633 return d->rootMode; 00634 } 00635 00636 TQCString KCModuleProxy::dcopName() const 00637 { 00638 return d->dcopName; 00639 } 00640 00641 void KCModuleProxy::emitQuickHelpChanged() 00642 { 00643 emit quickHelpChanged(); 00644 } 00645 00646 /***************************************************************/ 00647 #include "kcmoduleproxy.moc" 00648 00649 // vim: sw=4 ts=4 noet