diff options
Diffstat (limited to 'kutils/kcmoduleproxy.cpp')
-rw-r--r-- | kutils/kcmoduleproxy.cpp | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/kutils/kcmoduleproxy.cpp b/kutils/kcmoduleproxy.cpp new file mode 100644 index 000000000..3822db841 --- /dev/null +++ b/kutils/kcmoduleproxy.cpp @@ -0,0 +1,649 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Frans Englich <frans.englich@telia.com> + Copyright (C) 2003 Matthias Kretz <kretz@kde.org> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include <qapplication.h> +#include <qcursor.h> +#include <qdatastream.h> +#include <qevent.h> +#include <qfileinfo.h> +#include <qframe.h> +#include <qlabel.h> +#include <qlayout.h> +#include <qpoint.h> +#include <qscrollview.h> +#include <qtextstream.h> +#include <qvbox.h> +#include <qwhatsthis.h> +#include <qwidget.h> + +#include <dcopclient.h> +#include <qxembed.h> + +#include <kapplication.h> +#include <kaboutdata.h> +#include <kcmodule.h> +#include <kcmoduleinfo.h> +#include <kcmoduleloader.h> +#include <kdebug.h> +#include <kdialog.h> +#include <klocale.h> +#include <kprocess.h> +#include <kservice.h> +#include <kstandarddirs.h> +#include <kuser.h> + +#include <X11/Xlib.h> + +#include "kcmoduleproxy.h" +#include "kcmoduleproxyIface.h" +#include "kcmoduleproxyIfaceImpl.h" + +/***************************************************************/ +class KCModuleProxy::KCModuleProxyPrivate +{ + public: + KCModuleProxyPrivate( const KCModuleInfo & info ) + : args( 0 ) + , kcm( 0 ) + //, view( 0 ) + , embedWidget( 0 ) + , rootProcess ( 0 ) + , embedFrame ( 0 ) + , dcopObject( 0 ) + , dcopClient( 0 ) + , topLayout( 0 ) + , rootCommunicator( 0 ) + , rootInfo( 0 ) + , modInfo( info ) + , withFallback( false ) + , changed( false ) + , rootMode( false ) + , bogusOccupier( false ) + , isInitialized( false ) + {} + + ~KCModuleProxyPrivate() + { + delete rootInfo; // Delete before embedWidget! + delete embedWidget; // Delete before embedFrame! + delete embedFrame; + delete dcopClient; + delete dcopObject; + delete rootCommunicator; + delete rootProcess; + delete kcm; + } + + QStringList args; + KCModule *kcm; + QXEmbed *embedWidget; + KProcess *rootProcess; + QVBox *embedFrame; + KCModuleProxyIfaceImpl *dcopObject; + DCOPClient *dcopClient; + QVBoxLayout *topLayout; /* Contains QScrollView view, and root stuff */ + KCModuleProxyRootCommunicatorImpl *rootCommunicator; + QLabel *rootInfo; + QCString dcopName; + KCModuleInfo modInfo; + bool withFallback; + bool changed; + bool rootMode; + bool bogusOccupier; + bool isInitialized; +}; +/***************************************************************/ + + + +/* + TODO: + + - How KCModuleProxy behaves wrt memory leaks and behavior, when exiting + from root mode is not tested, because no code make use of it. It needs + work, if it should be used. + + - Should write a document which outlines test cases, to avoid + regressions. This class is a hazard. + + - Two Layout problems in runAsRoot: + * lblBusy doesn't show + * d->kcm/d->rootInfo doesn't get it right when the user + presses cancel in the kdesu dialog + + - Resizing horizontally is contrained; minimum size is set somewhere. + It appears to be somehow derived from the module's size. + + - Prettify: set icon in KCMultiDialog. + + - Perhaps it's possible to link against kdesu such that + the dialog is in process? + + */ +/***************************************************************/ +KCModule * KCModuleProxy::realModule() const +{ + + /* + * Note, don't call any function that calls realModule() since + * that leads to an infinite loop. + */ + + kdDebug(711) << k_funcinfo << endl; + + /* Already loaded */ + if( d->kcm ) + return d->kcm; + + /* /We/ have no kcm, but kcmshell running with root prevs does.. */ + if( d->rootMode ) + return 0; + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + KCModuleProxy * that = const_cast<KCModuleProxy*>( this ); + + if( !d->isInitialized ) + { + d->dcopName = moduleInfo().handle().prepend("KCModuleProxy-").utf8(); + d->topLayout = new QVBoxLayout( that, 0, 0, "topLayout" ); + + d->isInitialized = true; + } + + if( !d->dcopClient ) + d->dcopClient = new DCOPClient(); + + if( !d->dcopClient->isRegistered() ) + d->dcopClient->registerAs( d->dcopName, false ); + + d->dcopClient->setAcceptCalls( true ); + + if( d->dcopClient->appId() == d->dcopName || d->bogusOccupier ) + { /* We got the name we requested, because no one was before us, + * or, it was an random application which had picked that name */ + kdDebug(711) << "Module not already loaded, loading module" << endl; + + d->dcopObject = new KCModuleProxyIfaceImpl( d->dcopName, that ); + + d->kcm = KCModuleLoader::loadModule( moduleInfo(), KCModuleLoader::Inline, d->withFallback, + that, name(), d->args ); + + connect( d->kcm, SIGNAL( changed( bool ) ), + SLOT(moduleChanged(bool)) ); + connect( d->kcm, SIGNAL( destroyed() ), + SLOT( moduleDestroyed() ) ); + connect( d->kcm, SIGNAL(quickHelpChanged()), + SIGNAL(quickHelpChanged())); + QWhatsThis::add( that, d->kcm->quickHelp() ); + + d->topLayout->addWidget( d->kcm ); + + if ( !d->rootInfo && /* If it's already done */ + moduleInfo().needsRootPrivileges() /* root, anyone? */ && + !KUser().isSuperUser() ) /* Not necessary if we're root */ + { + + d->rootInfo = new QLabel( that, "rootInfo" ); + d->topLayout->insertWidget( 0, d->rootInfo ); + + d->rootInfo->setFrameShape(QFrame::Box); + d->rootInfo->setFrameShadow(QFrame::Raised); + + const QString msg = d->kcm->rootOnlyMsg(); + if( msg.isEmpty() ) + d->rootInfo->setText(i18n( + "<b>Changes in this section requires root access.</b><br />" + "Click the \"Administrator Mode\" button to " + "allow modifications.")); + else + d->rootInfo->setText(msg); + + QWhatsThis::add( d->rootInfo, i18n( + "This section requires special permissions, probably " + "for system-wide changes; therefore, it is " + "required that you provide the root password to be " + "able to change the module's properties. If " + "you do not provide the password, the module will be " + "disabled.")); + } + } + else + { + kdDebug(711) << "Module already loaded, loading KCMError" << endl; + + d->dcopClient->detach(); + /* Re-register as anonymous */ + d->dcopClient->attach(); + + d->dcopClient->setNotifications( true ); + connect( d->dcopClient, SIGNAL( applicationRemoved( const QCString& )), + SLOT( applicationRemoved( const QCString& ))); + + /* Figure out the name of where the module is already loaded */ + QByteArray replyData, data; + QCString replyType; + QString result; + QDataStream arg, stream( replyData, IO_ReadOnly ); + + if( d->dcopClient->call( d->dcopName, d->dcopName, "applicationName()", + data, replyType, replyData )) + { + stream >> result; + + d->kcm = KCModuleLoader::reportError( KCModuleLoader::Inline, + i18n( "Argument is application name", "This configuration section is " + "already opened in %1" ).arg( result ), " ", that ); + + d->topLayout->addWidget( d->kcm ); + } + else + { + kdDebug(711) << "Calling KCModuleProxy's DCOP interface for fetching the name failed." << endl; + d->bogusOccupier = true; + QApplication::restoreOverrideCursor(); + return realModule(); + } + } + + QApplication::restoreOverrideCursor(); + + return d->kcm; +} + +void KCModuleProxy::applicationRemoved( const QCString& app ) +{ + if( app == d->dcopName ) + { + /* Violence: Get rid of KCMError & CO, so that + * realModule() attempts to reload the module */ + delete d->kcm; + d->kcm = 0; + d->dcopClient->setNotifications( false ); + realModule(); + d->kcm->show(); + } +} + +void KCModuleProxy::showEvent( QShowEvent * ev ) +{ + + kdDebug(711) << k_funcinfo << endl; + ( void )realModule(); + + /* We have no kcm, if we're in root mode */ + if( d->kcm ) + d->kcm->show(); + + QWidget::showEvent( ev ); + +} + +void KCModuleProxy::runAsRoot() +{ + if ( !moduleInfo().needsRootPrivileges() ) + return; + + QApplication::setOverrideCursor( Qt::WaitCursor ); + + delete d->rootProcess; + delete d->embedWidget; + delete d->embedFrame; + + d->embedFrame = new QVBox( this, "embedFrame" ); + d->embedFrame->setFrameStyle( QFrame::Box | QFrame::Raised ); + + QPalette pal( red ); + pal.setColor( QColorGroup::Background, + colorGroup().background() ); + d->embedFrame->setPalette( pal ); + d->embedFrame->setLineWidth( 2 ); + d->embedFrame->setMidLineWidth( 2 ); + d->topLayout->addWidget(d->embedFrame,1); + + d->embedWidget = new QXEmbed( d->embedFrame, "embedWidget" ); + + d->embedFrame->show(); + + QLabel *lblBusy = new QLabel(i18n("<big>Loading...</big>"), d->embedWidget, "lblBusy" ); + lblBusy->setTextFormat(RichText); + lblBusy->setAlignment(AlignCenter); + lblBusy->setGeometry(0,0, d->kcm->width(), d->kcm->height()); + lblBusy->show(); + + deleteClient(); + /* The DCOP registration is now gone, and it will occur again when kcmshell soon + * registers. Here's a race condition in other words, but how likely is that? + * + * - It's a user initiated action, which means the user have to do weird stuff, very + * quick. + * - If the user _do_ manage to fsck up, the code will recover gracefully, see realModule(). + * + * So no worry. At the end of this function, communication with + * the DCOP object is established. + */ + + /* Prepare the process to run the kcmshell */ + QString cmd = moduleInfo().service()->exec().stripWhiteSpace(); + if (cmd.left(5) == "kdesu") + { + cmd = cmd.remove(0,5).stripWhiteSpace(); + + /* Remove all kdesu switches */ + while( cmd.length() > 1 && cmd[ 0 ] == '-' ) + cmd = cmd.remove( 0, cmd.find( ' ' ) ).stripWhiteSpace(); + } + + if (cmd.left(8) == "kcmshell") + cmd = cmd.remove(0,8).stripWhiteSpace(); + + /* Run the process */ + QString kdesu = KStandardDirs::findExe("kdesu"); + if (!kdesu.isEmpty()) + { + + d->rootProcess = new KProcess; + + *d->rootProcess << kdesu; + *d->rootProcess << "--nonewdcop" << "-n" << "-d" << QString( "-i%1" ).arg(moduleInfo().icon()); + + *d->rootProcess << QString("%1 %2 --embed-proxy %3 --lang %4").arg(locate("exe", "kcmshell")) + .arg(cmd).arg(d->embedWidget->winId()).arg(KGlobal::locale()->language()); + + connect(d->rootProcess, SIGNAL(processExited(KProcess*)), SLOT(rootExited())); + + if ( !d->rootProcess->start( KProcess::NotifyOnExit )) + { + d->rootMode = false; + rootExited(); + } + else + { + d->rootMode = true; + kapp->dcopClient(); + d->rootCommunicator = new KCModuleProxyRootCommunicatorImpl( d->dcopName + "-RootCommunicator", this ); + } + + delete lblBusy; + QApplication::restoreOverrideCursor(); + return; + } + + /* Clean up in case of failure */ + delete d->embedWidget; + d->embedWidget = 0; + delete d->embedFrame; + d->embedFrame = 0; + + QApplication::restoreOverrideCursor(); +} + +void KCModuleProxy::rootExited() +{ + kdDebug(711) << k_funcinfo << endl; + + if ( d->embedWidget->embeddedWinId() ) + XDestroyWindow(qt_xdisplay(), d->embedWidget->embeddedWinId()); + + delete d->embedWidget; + d->embedWidget = 0; + + delete d->rootProcess; + d->rootProcess = 0; + + delete d->embedFrame; + d->embedFrame=0; + + delete d->rootCommunicator; + d->rootCommunicator = 0; + + /* Such that the "ordinary" module loads again */ + d->rootMode = false; + + d->topLayout->invalidate(); + + QShowEvent ev; + showEvent( &ev ); + + moduleChanged( false ); + emit childClosed(); +} + +KCModuleProxy::~KCModuleProxy() +{ + deleteClient(); + KCModuleLoader::unloadModule(moduleInfo()); + + delete d; +} + +void KCModuleProxy::deleteClient() +{ + if( d->embedWidget ) + XKillClient(qt_xdisplay(), d->embedWidget->embeddedWinId()); + + + delete d->kcm; + d->kcm = 0; + + delete d->dcopObject; + d->dcopObject = 0; + + if( d->dcopClient && !d->dcopClient->detach() ) + kdDebug(711) << "Unregistering from DCOP failed." << endl; + + delete d->dcopClient; + d->dcopClient = 0; + + kapp->syncX(); + +} + +void KCModuleProxy::moduleChanged( bool c ) +{ + if( d->changed == c ) + return; + + d->changed = c; + emit changed( c ); + emit changed( this ); +} + +void KCModuleProxy::moduleDestroyed() +{ + d->kcm = 0; +} + +KCModuleProxy::KCModuleProxy( const KService::Ptr & service, bool withFallback, + QWidget * parent, const char * name, const QStringList & args) + : QWidget( parent, name ) +{ + init( KCModuleInfo( service )); + d->args = args; + d->withFallback = withFallback; +} + +KCModuleProxy::KCModuleProxy( const KCModuleInfo & info, bool withFallback, + QWidget * parent, const char * name, const QStringList & args ) + : QWidget( parent, name ) +{ + init( info ); + d->args = args; + d->withFallback = withFallback; +} + +KCModuleProxy::KCModuleProxy( const QString& serviceName, bool withFallback, + QWidget * parent, const char * name, + const QStringList & args) + : QWidget( parent, name ) +{ + init( KCModuleInfo( serviceName )); + d->args = args; + d->withFallback = withFallback; +} + +void KCModuleProxy::init( const KCModuleInfo& info ) +{ + kdDebug(711) << k_funcinfo << endl; + + d = new KCModuleProxyPrivate( info ); + + /* This is all we do for now; all the heavy work is + * done in realModule(). It's called when the module + * _actually_ is needed, in for example showEvent(). + * The module is loaded "on demand" -- lazy loading. + */ + +} + +void KCModuleProxy::load() +{ + + if( d->rootMode ) + callRootModule( "load()" ); + else if( realModule() ) + { + d->kcm->load(); + moduleChanged( false ); + } +} + +void KCModuleProxy::save() +{ + if( d->rootMode ) + callRootModule( "save()" ); + else if( d->changed && realModule() ) + { + d->kcm->save(); + moduleChanged( false ); + } +} + +void KCModuleProxy::callRootModule( const QCString& function ) +{ + QByteArray sendData, replyData; + QCString replyType; + + /* Note, we don't use d->dcopClient here, because it's used for + * the loaded module(and it's not "us" when this function is called) */ + if( !kapp->dcopClient()->call( d->dcopName, d->dcopName, function, sendData, + replyType, replyData, true, -1 )) + kdDebug(711) << "Calling function '" << function << "' failed." << endl; + +} + +void KCModuleProxy::defaults() +{ + if( d->rootMode ) + callRootModule( "defaults()" ); + if( realModule() ) + d->kcm->defaults(); +} + +QString KCModuleProxy::quickHelp() const +{ + + if( !d->rootMode ) + return realModule() ? realModule()->quickHelp() : QString::null; + else + { + QByteArray data, replyData; + QCString replyType; + + if (kapp->dcopClient()->call(d->dcopName, d->dcopName, "quickHelp()", + data, replyType, replyData)) + kdDebug(711) << "Calling DCOP function bool changed() failed." << endl; + else + { + QDataStream reply(replyData, IO_ReadOnly); + if (replyType == "QString") + { + QString result; + reply >> result; + return result; + } + else + kdDebug(711) << "DCOP function changed() returned mumbo jumbo." << endl; + } + return QString::null; + } +} + +const KAboutData * KCModuleProxy::aboutData() const +{ + if( !d->rootMode ) + return realModule() ? realModule()->aboutData() : 0; + else + /* This needs fixing, perhaps cache a KAboutData copy + * while in root mode? */ + return 0; + + +} + +int KCModuleProxy::buttons() const +{ + return realModule() ? realModule()->buttons() : + KCModule::Help | KCModule::Default | KCModule::Apply ; +} + +QString KCModuleProxy::rootOnlyMsg() const +{ + return realModule() ? realModule()->rootOnlyMsg() : QString::null; +} + +bool KCModuleProxy::useRootOnlyMsg() const +{ + return realModule() ? realModule()->useRootOnlyMsg() : true; +} + +KInstance * KCModuleProxy::instance() const +{ + return realModule() ? realModule()->instance() : 0; +} + +bool KCModuleProxy::changed() const +{ + return d->changed; +} + +const KCModuleInfo& KCModuleProxy::moduleInfo() const +{ + return d->modInfo; +} + +bool KCModuleProxy::rootMode() const +{ + return d->rootMode; +} + +QCString KCModuleProxy::dcopName() const +{ + return d->dcopName; +} + +void KCModuleProxy::emitQuickHelpChanged() +{ + emit quickHelpChanged(); +} + +/***************************************************************/ +#include "kcmoduleproxy.moc" + +// vim: sw=4 ts=4 noet |