Vidalia  0.2.17
Vidalia.cpp
Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.torproject.org/projects/vidalia.html. No part of Vidalia, 
00007 **  including this file, may be copied, modified, propagated, or distributed 
00008 **  except according to the terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file Vidalia.cpp
00013 ** \brief Main Vidalia QApplication object
00014 */
00015 
00016 #include "config.h"
00017 #include "Vidalia.h"
00018 #include "LanguageSupport.h"
00019 #include "VMessageBox.h"
00020 
00021 #include "stringutil.h"
00022 #include "html.h"
00023 
00024 #ifdef USE_MARBLE
00025 #include <MarbleDirs.h>
00026 #endif
00027 
00028 #include <QDir>
00029 #include <QTimer>
00030 #include <QTextStream>
00031 #include <QStyleFactory>
00032 #include <QShortcut>
00033 #include <QTranslator>
00034 #include <QLibraryInfo>
00035 #include <QSslSocket>
00036 
00037 #ifdef Q_OS_MACX
00038 #include <Carbon/Carbon.h>
00039 #endif
00040 #include <stdlib.h>
00041 
00042 /* Available command-line arguments. */
00043 #define ARG_LANGUAGE   "lang"     /**< Argument specifying language.    */
00044 #define ARG_GUISTYLE   "style"    /**< Argument specfying GUI style.    */
00045 #define ARG_RESET      "reset"    /**< Reset Vidalia's saved settings.  */
00046 #define ARG_HELP       "help"     /**< Display usage informatino.       */
00047 #define ARG_DATADIR    "datadir"  /**< Directory to use for data files. */
00048 #define ARG_PIDFILE    "pidfile"  /**< Location and name of our pidfile.*/
00049 #define ARG_LOGFILE    "logfile"  /**< Location of our logfile.         */
00050 #define ARG_LOGLEVEL   "loglevel" /**< Log verbosity.                   */
00051 #define ARG_READ_PASSWORD_FROM_STDIN  \
00052   "read-password-from-stdin" /**< Read password from stdin. */
00053 
00054 /* Static member variables */
00055 QMap<QString, QString> Vidalia::_args; /**< List of command-line arguments.  */
00056 QString Vidalia::_style;               /**< The current GUI style.           */
00057 QString Vidalia::_language;            /**< The current language.            */
00058 TorControl* Vidalia::_torControl = 0;  /**< Main TorControl object.          */
00059 Log Vidalia::_log;
00060 QList<QTranslator *> Vidalia::_translators;
00061 
00062 
00063 /** Catches debugging messages from Qt and sends them to Vidalia's logs. If Qt
00064  * emits a QtFatalMsg, we will write the message to the log and then abort().
00065  */
00066 void
00067 Vidalia::qt_msg_handler(QtMsgType type, const char *s)
00068 {
00069   QString msg(s);
00070   switch (type) {
00071     case QtDebugMsg:
00072       vDebug("QtDebugMsg: %1").arg(msg);
00073       break;
00074     case QtWarningMsg:
00075       vNotice("QtWarningMsg: %1").arg(msg);
00076       break;
00077     case QtCriticalMsg:
00078       vWarn("QtCriticalMsg: %1").arg(msg);
00079       break;
00080     case QtFatalMsg:
00081       vError("QtFatalMsg: %1").arg(msg);
00082       break;
00083   }
00084   if (type == QtFatalMsg) {
00085     vError("Fatal Qt error. Aborting.");
00086     abort();
00087   }
00088 }
00089 
00090 /** Constructor. Parses the command-line arguments, resets Vidalia's
00091  * configuration (if requested), and sets up the GUI style and language
00092  * translation. */
00093 Vidalia::Vidalia(QStringList args, int &argc, char **argv)
00094 : QApplication(argc, argv)
00095 {
00096   qInstallMsgHandler(qt_msg_handler);
00097 
00098   /* Read in all our command-line arguments. */
00099   parseArguments(args);
00100 
00101   /* Check if we're supposed to reset our config before proceeding. */
00102   if (_args.contains(ARG_RESET))
00103     VidaliaSettings::reset();
00104 
00105   /* See if we should load a default configuration file. */
00106   if (! VidaliaSettings::settingsFileExists())
00107     copyDefaultSettingsFile();
00108 
00109   /* Handle the -loglevel and -logfile options. */
00110   if (_args.contains(ARG_LOGFILE))
00111     _log.open(_args.value(ARG_LOGFILE));
00112   if (_args.contains(ARG_LOGLEVEL)) {
00113     _log.setLogLevel(Log::stringToLogLevel(
00114                       _args.value(ARG_LOGLEVEL)));
00115     if (!_args.contains(ARG_LOGFILE))
00116       _log.open(stdout);
00117   }
00118   if (!_args.contains(ARG_LOGLEVEL) && 
00119       !_args.contains(ARG_LOGFILE))
00120     _log.setLogLevel(Log::Off);
00121 
00122   /* Translate the GUI to the appropriate language. */
00123   setLanguage(_args.value(ARG_LANGUAGE));
00124   /* Set the GUI style appropriately. */
00125   setStyle(_args.value(ARG_GUISTYLE));
00126 
00127   /* Creates a TorControl object, used to talk to Tor. */
00128   _torControl = new TorControl(TorSettings().getControlMethod());
00129 
00130   /* If we were built with QSslSocket support, then populate the default
00131    * CA certificate store. */
00132   loadDefaultCaCertificates();
00133 
00134 #ifdef USE_MARBLE
00135   /* Tell Marble where to stash its generated data */
00136   Marble::MarbleDirs::setMarbleDataPath(dataDirectory());
00137 
00138 #ifdef Q_OS_WIN32
00139   Marble::MarbleDirs::setMarblePluginPath(vApp->applicationDirPath() 
00140                                             + "/plugins/marble");
00141 #endif
00142 #endif
00143 #ifdef Q_WS_MAC
00144   setStyleSheet("QTreeWidget { font-size: 12pt }");
00145 #endif
00146 }
00147 
00148 /** Destructor */
00149 Vidalia::~Vidalia()
00150 {
00151   delete _torControl;
00152 }
00153 
00154 /** Enters the main event loop and waits until exit() is called. The signal
00155  * running() will be emitted when the event loop has started. */
00156 int
00157 Vidalia::run()
00158 {
00159   QTimer::singleShot(0, vApp, SLOT(onEventLoopStarted()));
00160   return vApp->exec();
00161 }
00162 
00163 /** Called when the application's main event loop has started. This method
00164  * will emit the running() signal to indicate that the application's event
00165  * loop is running. */
00166 void
00167 Vidalia::onEventLoopStarted()
00168 {
00169   emit running();
00170 }
00171 
00172 #if defined(Q_OS_WIN)
00173 /** On Windows, we need to catch the WM_QUERYENDSESSION message
00174  * so we know that it is time to shutdown. */
00175 bool
00176 Vidalia::winEventFilter(MSG *msg, long *result)
00177 {
00178   if (msg->message == WM_QUERYENDSESSION) {
00179     quit();
00180   }
00181   return QApplication::winEventFilter(msg, result);
00182 }
00183 #endif
00184 
00185 /** Returns true if the user wants to see usage information. */
00186 bool
00187 Vidalia::showUsage()
00188 {
00189   return _args.contains(ARG_HELP);
00190 }
00191 
00192 /** Displays usage information for command-line args. */
00193 void
00194 Vidalia::showUsageMessageBox()
00195 {
00196   QString usage;
00197   QTextStream out(&usage);
00198 
00199   out << "Available Options:" << endl;
00200   out << "<table>";
00201   out << trow(tcol("-"ARG_HELP) + 
00202               tcol(tr("Displays this usage message and exits.")));
00203   out << trow(tcol("-"ARG_RESET) +
00204               tcol(tr("Resets ALL stored Vidalia settings.")));
00205   out << trow(tcol("-"ARG_DATADIR" &lt;dir&gt;") +
00206               tcol(tr("Sets the directory Vidalia uses for data files.")));
00207   out << trow(tcol("-"ARG_PIDFILE" &lt;file&gt;") +
00208               tcol(tr("Sets the name and location of Vidalia's pidfile.")));
00209   out << trow(tcol("-"ARG_LOGFILE" &lt;file&gt;") +
00210               tcol(tr("Sets the name and location of Vidalia's logfile.")));
00211   out << trow(tcol("-"ARG_LOGLEVEL" &lt;level&gt;") +
00212               tcol(tr("Sets the verbosity of Vidalia's logging.") +
00213                    "<br>[" + Log::logLevels().join("|") +"]"));
00214   out << trow(tcol("-"ARG_GUISTYLE" &lt;style&gt;") +
00215               tcol(tr("Sets Vidalia's interface style.") +
00216                    "<br>[" + QStyleFactory::keys().join("|") + "]"));
00217   out << trow(tcol("-"ARG_LANGUAGE" &lt;language&gt;") + 
00218               tcol(tr("Sets Vidalia's language.") +
00219                    "<br>[" + LanguageSupport::languageCodes().join("|") + "]"));
00220   out << "</table>";
00221 
00222   VMessageBox::information(0, 
00223     tr("Vidalia Usage Information"), usage, VMessageBox::Ok);
00224 }
00225 
00226 /** Returns true if the specified argument expects a value. */
00227 bool
00228 Vidalia::argNeedsValue(QString argName)
00229 {
00230   return (argName == ARG_GUISTYLE ||
00231           argName == ARG_LANGUAGE ||
00232           argName == ARG_DATADIR  ||
00233           argName == ARG_PIDFILE  ||
00234           argName == ARG_LOGFILE  ||
00235           argName == ARG_LOGLEVEL);
00236 }
00237 
00238 /** Parses the list of command-line arguments for their argument names and
00239  * values. */
00240 void
00241 Vidalia::parseArguments(QStringList args)
00242 {
00243   QString arg, value;
00244 
00245   /* Loop through all command-line args/values and put them in a map */
00246   for (int i = 0; i < args.size(); i++) {
00247     /* Get the argument name and set a blank value */
00248     arg   = args.at(i).toLower();
00249     value = "";
00250 
00251     /* Check if it starts with a - or -- */
00252     if (arg.startsWith("-")) {
00253       arg = arg.mid((arg.startsWith("--") ? 2 : 1));
00254     }
00255     /* Argument names do not include equal sign. Assume value follows. */
00256     if (arg.indexOf("=") > -1) {
00257       value = arg.right(arg.length() - (arg.indexOf("=")+1));
00258       arg = arg.left(arg.indexOf("="));
00259     }
00260     else
00261     /* Check if it takes a value and there is one on the command-line */
00262     if (i < args.size()-1 && argNeedsValue(arg)) {
00263       value = args.at(++i);
00264     }
00265     /* Place this arg/value in the map */
00266     _args.insert(arg, value);
00267   }
00268 }
00269 
00270 /** Verifies that all specified arguments were valid. */
00271 bool
00272 Vidalia::validateArguments(QString &errmsg)
00273 {
00274   /* Check for missing parameter values */
00275   QMapIterator<QString, QString> _i(_args);
00276   QString tmp;
00277   while(_i.hasNext()) {
00278     _i.next();
00279     if(argNeedsValue(_i.key()) && (_i.value() == "")) {
00280       errmsg = tr("Value required for parameter :") + _i.key();
00281       return false;
00282     }
00283   }
00284   /* Check for a language that Vidalia recognizes. */
00285   if (_args.contains(ARG_LANGUAGE) &&
00286       !LanguageSupport::isValidLanguageCode(_args.value(ARG_LANGUAGE))) {
00287     errmsg = tr("Invalid language code specified: ") + _args.value(ARG_LANGUAGE);
00288     return false;
00289   }
00290   /* Check for a valid GUI style */
00291   if (_args.contains(ARG_GUISTYLE) &&
00292       !QStyleFactory::keys().contains(_args.value(ARG_GUISTYLE),
00293                                       Qt::CaseInsensitive)) {
00294     errmsg = tr("Invalid GUI style specified: ") + _args.value(ARG_GUISTYLE);
00295     return false;
00296   }
00297   /* Check for a valid log level */
00298   if (_args.contains(ARG_LOGLEVEL) &&
00299       !Log::logLevels().contains(_args.value(ARG_LOGLEVEL))) {
00300     errmsg = tr("Invalid log level specified: ") + _args.value(ARG_LOGLEVEL);
00301     return false;
00302   }
00303   /* Check for a writable log file */
00304   if (_args.contains(ARG_LOGFILE) && !_log.isOpen()) {
00305     errmsg = tr("Unable to open log file '%1': %2")
00306                            .arg(_args.value(ARG_LOGFILE))
00307                            .arg(_log.errorString());
00308     return false;
00309   }
00310   return true;
00311 }
00312 
00313 /** Sets the translation Vidalia will use. If one was specified on the
00314  * command-line, we will use that. Otherwise, we'll check to see if one was
00315  * saved previously. If not, we'll default to one appropriate for the system
00316  * locale. */
00317 bool
00318 Vidalia::setLanguage(QString languageCode)
00319 {
00320   /* If the language code is empty, use the previously-saved setting */
00321   if (languageCode.isEmpty()) {
00322     VidaliaSettings settings;
00323     languageCode = settings.getLanguageCode();
00324   }
00325   /* Translate into the desired langauge */
00326   if (retranslateUi(languageCode)) {
00327     _language = languageCode;
00328     return true;
00329   }
00330   return false;
00331 }
00332 
00333 /** Sets the GUI style Vidalia will use. If one was specified on the
00334  * command-line, we will use that. Otherwise, we'll check to see if one was
00335  * saved previously. If not, we'll default to one appropriate for the
00336  * operating system. */
00337 bool
00338 Vidalia::setStyle(QString styleKey)
00339 {
00340   /* If no style was specified, use the previously-saved setting */
00341   if (styleKey.isEmpty()) {
00342     VidaliaSettings settings;
00343     styleKey = settings.getInterfaceStyle();
00344   }
00345   /* Apply the specified GUI style */
00346   if (QApplication::setStyle(styleKey)) {
00347     _style = styleKey;
00348     return true;
00349   }
00350   return false;
00351 }
00352 
00353 /** Returns the directory Vidalia uses for its data files. */
00354 QString
00355 Vidalia::dataDirectory()
00356 {
00357   if (_args.contains(ARG_DATADIR)) {
00358     return _args.value(ARG_DATADIR);
00359   }
00360   return defaultDataDirectory();
00361 }
00362 
00363 /** Returns the default location of Vidalia's data directory. */
00364 QString
00365 Vidalia::defaultDataDirectory()
00366 {
00367 #if defined(Q_OS_WIN32)
00368   return (win32_app_data_folder() + "\\Vidalia");
00369 #elif defined(Q_OS_MAC)
00370   return (QDir::homePath() + "/Library/Vidalia");
00371 #else
00372   return (QDir::homePath() + "/.vidalia");
00373 #endif
00374 }
00375 
00376 /** Returns the location of Vidalia's pid file. */
00377 QString
00378 Vidalia::pidFile()
00379 {
00380   if (_args.contains(ARG_PIDFILE)) {
00381     return _args.value(ARG_PIDFILE);
00382   }
00383   return QDir::convertSeparators(dataDirectory() + "/vidalia.pid");
00384 }
00385 
00386 bool
00387 Vidalia::readPasswordFromStdin()
00388 {
00389   return _args.contains(ARG_READ_PASSWORD_FROM_STDIN);
00390 }
00391 
00392 /** Writes <b>msg</b> with severity <b>level</b> to Vidalia's log. */
00393 Log::LogMessage
00394 Vidalia::log(Log::LogLevel level, QString msg)
00395 {
00396   return _log.log(level, msg);
00397 }
00398 
00399 /** Creates and binds a shortcut such that when <b>key</b> is pressed in
00400  * <b>sender</b>'s context, <b>receiver</b>'s <b>slot</b> will be called. */
00401 void
00402 Vidalia::createShortcut(const QKeySequence &key, QWidget *sender,
00403                         QObject *receiver, const char *slot)
00404 {
00405   QShortcut *s = new QShortcut(key, sender);
00406   connect(s, SIGNAL(activated()), receiver, slot);
00407 }
00408 
00409 /** Creates and binds a shortcut such that when <b>key</b> is pressed in
00410  * <b>sender</b>'s context, <b>receiver</b>'s <b>slot</b> will be called. */
00411 void
00412 Vidalia::createShortcut(const QString &key, QWidget *sender,
00413                         QObject *receiver, const char *slot)
00414 {
00415   createShortcut(QKeySequence(key), sender, receiver, slot);
00416 }
00417 
00418 void
00419 Vidalia::removeAllTranslators()
00420 {
00421   vInfo("Removing all currently installed UI translator objects.");
00422   foreach (QTranslator *translator, _translators) {
00423     QApplication::removeTranslator(translator);
00424     delete translator;
00425   }
00426   _translators.clear();
00427 }
00428 
00429 bool
00430 Vidalia::retranslateUi(const QString &languageCode)
00431 {
00432   QTranslator *systemQtTranslator = 0;
00433   QTranslator *vidaliaQtTranslator = 0;
00434   QTranslator *vidaliaTranslator = 0;
00435 
00436   if (! LanguageSupport::isValidLanguageCode(languageCode)) {
00437     vWarn("Invalid language code: %1").arg(languageCode);
00438     return false;
00439   }
00440   if (! languageCode.compare("en", Qt::CaseInsensitive)) {
00441     vNotice("Resetting UI translation to English default.");
00442     _language = languageCode;
00443     removeAllTranslators();
00444     return true;
00445   }
00446 
00447   systemQtTranslator = new QTranslator(vApp);
00448   Q_CHECK_PTR(systemQtTranslator);
00449   QString qtDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
00450   systemQtTranslator->load(qtDir + "/qt_" + languageCode + ".qm");
00451 
00452 
00453   vidaliaQtTranslator = new QTranslator(vApp);
00454   Q_CHECK_PTR(vidaliaQtTranslator);
00455   vidaliaQtTranslator->load(":/lang/qt_" + languageCode + ".qm");
00456 
00457   vidaliaTranslator = new QTranslator(vApp);
00458   Q_CHECK_PTR(vidaliaTranslator);
00459   if (! vidaliaTranslator->load(":/lang/vidalia_" + languageCode + ".qm"))
00460     goto err;
00461 
00462   removeAllTranslators();
00463   vNotice("Changing UI translation from '%1' to '%2'").arg(_language)
00464                                                       .arg(languageCode);
00465   _language = languageCode;
00466   QApplication::installTranslator(systemQtTranslator);
00467   QApplication::installTranslator(vidaliaQtTranslator);
00468   QApplication::installTranslator(vidaliaTranslator);
00469   _translators << systemQtTranslator
00470                << vidaliaQtTranslator
00471                << vidaliaTranslator;
00472 
00473   return true;
00474 
00475 err:
00476   vWarn("Unable to set UI translation to '%1'").arg(languageCode);
00477   if (systemQtTranslator)
00478     delete systemQtTranslator;
00479   if (vidaliaQtTranslator)
00480     delete vidaliaQtTranslator;
00481   if (vidaliaTranslator)
00482     delete vidaliaTranslator;
00483   delete vidaliaTranslator;
00484   return false;
00485 }
00486 
00487 /** Copies a default settings file (if one exists) to Vidalia's data
00488  * directory. */
00489 void
00490 Vidalia::copyDefaultSettingsFile() const
00491 {
00492 #ifdef Q_OS_MACX
00493   CFURLRef confUrlRef;
00494   CFStringRef pathRef;
00495   const char *path;
00496 
00497   confUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), 
00498                                        CFSTR("vidalia"), CFSTR("conf"), NULL);
00499   if (confUrlRef == NULL)
00500     return;
00501 
00502   pathRef = CFURLCopyFileSystemPath(confUrlRef, kCFURLPOSIXPathStyle);
00503   path    = CFStringGetCStringPtr(pathRef, CFStringGetSystemEncoding());
00504 
00505   if (path) {
00506     QString defaultConfFile = QString::fromLocal8Bit(path);
00507     QFileInfo fi(defaultConfFile);
00508     if (fi.exists()) {
00509       QFileInfo out(VidaliaSettings::settingsFile());
00510       if (! out.dir().exists())
00511         out.dir().mkpath(".");
00512       QFile::copy(defaultConfFile, out.absoluteFilePath());
00513     }
00514   }
00515   CFRelease(confUrlRef);
00516   CFRelease(pathRef);
00517 #endif
00518 }
00519 
00520 void
00521 Vidalia::loadDefaultCaCertificates() const
00522 {
00523   QSslSocket::setDefaultCaCertificates(QList<QSslCertificate>());
00524 
00525   if (! QSslSocket::addDefaultCaCertificates(":/pki/EquifaxSecureCA.crt"))
00526     vWarn("Failed to add the Equifax Secure CA certificate to the default CA "
00527           "certificate database.");
00528   if (! QSslSocket::addDefaultCaCertificates(":/pki/DigiCertCA.crt"))
00529     vWarn("Failed to add the DigiCert Global CA certificate to the default CA "
00530           "certificate database.");
00531   if (! QSslSocket::addDefaultCaCertificates(":/pki/DigiCertAssuredCA.crt"))
00532     vWarn("Failed to add the DigiCert Assured CA certificate to the default CA "
00533           "certificate database.");
00534   if (! QSslSocket::addDefaultCaCertificates(":/pki/DigiCertHighAssuranceCA.crt"))
00535     vWarn("Failed to add the DigiCert High Assurance CA certificate to the default CA "
00536           "certificate database.");
00537 }
00538