Vidalia 0.2.12

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.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to the
00008 **  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     /* Check if it takes a value and there is one on the command-line */
00256     if (i < args.size()-1 && argNeedsValue(arg)) {
00257       value = args.at(++i);
00258     }
00259     /* Place this arg/value in the map */
00260     _args.insert(arg, value);
00261   }
00262 }
00263 
00264 /** Verifies that all specified arguments were valid. */
00265 bool
00266 Vidalia::validateArguments(QString &errmsg)
00267 {
00268   /* Check for a language that Vidalia recognizes. */
00269   if (_args.contains(ARG_LANGUAGE) &&
00270       !LanguageSupport::isValidLanguageCode(_args.value(ARG_LANGUAGE))) {
00271     errmsg = tr("Invalid language code specified: ") + _args.value(ARG_LANGUAGE);
00272     return false;
00273   }
00274   /* Check for a valid GUI style */
00275   if (_args.contains(ARG_GUISTYLE) &&
00276       !QStyleFactory::keys().contains(_args.value(ARG_GUISTYLE),
00277                                       Qt::CaseInsensitive)) {
00278     errmsg = tr("Invalid GUI style specified: ") + _args.value(ARG_GUISTYLE);
00279     return false;
00280   }
00281   /* Check for a valid log level */
00282   if (_args.contains(ARG_LOGLEVEL) &&
00283       !Log::logLevels().contains(_args.value(ARG_LOGLEVEL))) {
00284     errmsg = tr("Invalid log level specified: ") + _args.value(ARG_LOGLEVEL);
00285     return false;
00286   }
00287   /* Check for a writable log file */
00288   if (_args.contains(ARG_LOGFILE) && !_log.isOpen()) {
00289     errmsg = tr("Unable to open log file '%1': %2")
00290                            .arg(_args.value(ARG_LOGFILE))
00291                            .arg(_log.errorString());
00292     return false;
00293   }
00294   return true;
00295 }
00296 
00297 /** Sets the translation Vidalia will use. If one was specified on the
00298  * command-line, we will use that. Otherwise, we'll check to see if one was
00299  * saved previously. If not, we'll default to one appropriate for the system
00300  * locale. */
00301 bool
00302 Vidalia::setLanguage(QString languageCode)
00303 {
00304   /* If the language code is empty, use the previously-saved setting */
00305   if (languageCode.isEmpty()) {
00306     VidaliaSettings settings;
00307     languageCode = settings.getLanguageCode();
00308   }
00309   /* Translate into the desired langauge */
00310   if (retranslateUi(languageCode)) {
00311     _language = languageCode;
00312     return true;
00313   }
00314   return false;
00315 }
00316 
00317 /** Sets the GUI style Vidalia will use. If one was specified on the
00318  * command-line, we will use that. Otherwise, we'll check to see if one was
00319  * saved previously. If not, we'll default to one appropriate for the
00320  * operating system. */
00321 bool
00322 Vidalia::setStyle(QString styleKey)
00323 {
00324   /* If no style was specified, use the previously-saved setting */
00325   if (styleKey.isEmpty()) {
00326     VidaliaSettings settings;
00327     styleKey = settings.getInterfaceStyle();
00328   }
00329   /* Apply the specified GUI style */
00330   if (QApplication::setStyle(styleKey)) {
00331     _style = styleKey;
00332     return true;
00333   }
00334   return false;
00335 }
00336 
00337 /** Returns the directory Vidalia uses for its data files. */
00338 QString
00339 Vidalia::dataDirectory()
00340 {
00341   if (_args.contains(ARG_DATADIR)) {
00342     return _args.value(ARG_DATADIR);
00343   }
00344   return defaultDataDirectory();
00345 }
00346 
00347 /** Returns the default location of Vidalia's data directory. */
00348 QString
00349 Vidalia::defaultDataDirectory()
00350 {
00351 #if defined(Q_OS_WIN32)
00352   return (win32_app_data_folder() + "\\Vidalia");
00353 #elif defined(Q_OS_MAC)
00354   return (QDir::homePath() + "/Library/Vidalia");
00355 #else
00356   return (QDir::homePath() + "/.vidalia");
00357 #endif
00358 }
00359 
00360 /** Returns the location of Vidalia's pid file. */
00361 QString
00362 Vidalia::pidFile()
00363 {
00364   if (_args.contains(ARG_PIDFILE)) {
00365     return _args.value(ARG_PIDFILE);
00366   }
00367   return QDir::convertSeparators(dataDirectory() + "/vidalia.pid");
00368 }
00369 
00370 bool
00371 Vidalia::readPasswordFromStdin()
00372 {
00373   return _args.contains(ARG_READ_PASSWORD_FROM_STDIN);
00374 }
00375 
00376 /** Writes <b>msg</b> with severity <b>level</b> to Vidalia's log. */
00377 Log::LogMessage
00378 Vidalia::log(Log::LogLevel level, QString msg)
00379 {
00380   return _log.log(level, msg);
00381 }
00382 
00383 /** Creates and binds a shortcut such that when <b>key</b> is pressed in
00384  * <b>sender</b>'s context, <b>receiver</b>'s <b>slot</b> will be called. */
00385 void
00386 Vidalia::createShortcut(const QKeySequence &key, QWidget *sender,
00387                         QObject *receiver, const char *slot)
00388 {
00389   QShortcut *s = new QShortcut(key, sender);
00390   connect(s, SIGNAL(activated()), receiver, slot);
00391 }
00392 
00393 /** Creates and binds a shortcut such that when <b>key</b> is pressed in
00394  * <b>sender</b>'s context, <b>receiver</b>'s <b>slot</b> will be called. */
00395 void
00396 Vidalia::createShortcut(const QString &key, QWidget *sender,
00397                         QObject *receiver, const char *slot)
00398 {
00399   createShortcut(QKeySequence(key), sender, receiver, slot);
00400 }
00401 
00402 void
00403 Vidalia::removeAllTranslators()
00404 {
00405   vInfo("Removing all currently installed UI translator objects.");
00406   foreach (QTranslator *translator, _translators) {
00407     QApplication::removeTranslator(translator);
00408     delete translator;
00409   }
00410   _translators.clear();
00411 }
00412 
00413 bool
00414 Vidalia::retranslateUi(const QString &languageCode)
00415 {
00416   QTranslator *systemQtTranslator = 0;
00417   QTranslator *vidaliaQtTranslator = 0;
00418   QTranslator *vidaliaTranslator = 0;
00419 
00420   if (! LanguageSupport::isValidLanguageCode(languageCode)) {
00421     vWarn("Invalid language code: %1").arg(languageCode);
00422     return false;
00423   }
00424   if (! languageCode.compare("en", Qt::CaseInsensitive)) {
00425     vNotice("Resetting UI translation to English default.");
00426     _language = languageCode;
00427     removeAllTranslators();
00428     return true;
00429   }
00430 
00431   systemQtTranslator = new QTranslator(vApp);
00432   Q_CHECK_PTR(systemQtTranslator);
00433   QString qtDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
00434   systemQtTranslator->load(qtDir + "/qt_" + languageCode + ".qm");
00435 
00436 
00437   vidaliaQtTranslator = new QTranslator(vApp);
00438   Q_CHECK_PTR(vidaliaQtTranslator);
00439   vidaliaQtTranslator->load(":/lang/qt_" + languageCode + ".qm");
00440 
00441   vidaliaTranslator = new QTranslator(vApp);
00442   Q_CHECK_PTR(vidaliaTranslator);
00443   if (! vidaliaTranslator->load(":/lang/vidalia_" + languageCode + ".qm"))
00444     goto err;
00445 
00446   removeAllTranslators();
00447   vNotice("Changing UI translation from '%1' to '%2'").arg(_language)
00448                                                       .arg(languageCode);
00449   _language = languageCode;
00450   QApplication::installTranslator(systemQtTranslator);
00451   QApplication::installTranslator(vidaliaQtTranslator);
00452   QApplication::installTranslator(vidaliaTranslator);
00453   _translators << systemQtTranslator
00454                << vidaliaQtTranslator
00455                << vidaliaTranslator;
00456 
00457   return true;
00458 
00459 err:
00460   vWarn("Unable to set UI translation to '%1'").arg(languageCode);
00461   if (systemQtTranslator)
00462     delete systemQtTranslator;
00463   if (vidaliaQtTranslator)
00464     delete vidaliaQtTranslator;
00465   if (vidaliaTranslator)
00466     delete vidaliaTranslator;
00467   delete vidaliaTranslator;
00468   return false;
00469 }
00470 
00471 /** Copies a default settings file (if one exists) to Vidalia's data
00472  * directory. */
00473 void
00474 Vidalia::copyDefaultSettingsFile() const
00475 {
00476 #ifdef Q_OS_MACX
00477   CFURLRef confUrlRef;
00478   CFStringRef pathRef;
00479   const char *path;
00480 
00481   confUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), 
00482                                        CFSTR("vidalia"), CFSTR("conf"), NULL);
00483   if (confUrlRef == NULL)
00484     return;
00485 
00486   pathRef = CFURLCopyFileSystemPath(confUrlRef, kCFURLPOSIXPathStyle);
00487   path    = CFStringGetCStringPtr(pathRef, CFStringGetSystemEncoding());
00488 
00489   if (path) {
00490     QString defaultConfFile = QString::fromLocal8Bit(path);
00491     QFileInfo fi(defaultConfFile);
00492     if (fi.exists()) {
00493       QFileInfo out(VidaliaSettings::settingsFile());
00494       if (! out.dir().exists())
00495         out.dir().mkpath(".");
00496       QFile::copy(defaultConfFile, out.absoluteFilePath());
00497     }
00498   }
00499   CFRelease(confUrlRef);
00500   CFRelease(pathRef);
00501 #endif
00502 }
00503 
00504 void
00505 Vidalia::loadDefaultCaCertificates() const
00506 {
00507   QSslSocket::setDefaultCaCertificates(QList<QSslCertificate>());
00508 
00509   if (! QSslSocket::addDefaultCaCertificates(":/pki/EquifaxSecureCA.crt"))
00510     vWarn("Failed to add the Equifax Secure CA certificate to the default CA "
00511           "certificate database.");
00512 }
00513