Vidalia  0.2.21
Vidalia.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file Vidalia.cpp
13 ** \brief Main Vidalia QApplication object
14 */
15 
16 #include "config.h"
17 #include "Vidalia.h"
18 #include "LanguageSupport.h"
19 #include "VMessageBox.h"
20 
21 #include "stringutil.h"
22 #include "html.h"
23 
24 #ifdef USE_MARBLE
25 #include <MarbleDirs.h>
26 #endif
27 
28 #include <QDir>
29 #include <QTimer>
30 #include <QTextStream>
31 #include <QStyleFactory>
32 #include <QShortcut>
33 #include <QTranslator>
34 #include <QLibraryInfo>
35 
36 #ifdef Q_OS_MACX
37 #include <Carbon/Carbon.h>
38 #endif
39 #include <stdlib.h>
40 
41 /* Available command-line arguments. */
42 #define ARG_LANGUAGE "lang" /**< Argument specifying language. */
43 #define ARG_GUISTYLE "style" /**< Argument specfying GUI style. */
44 #define ARG_RESET "reset" /**< Reset Vidalia's saved settings. */
45 #define ARG_HELP "help" /**< Display usage informatino. */
46 #define ARG_DATADIR "datadir" /**< Directory to use for data files. */
47 #define ARG_PIDFILE "pidfile" /**< Location and name of our pidfile.*/
48 #define ARG_LOGFILE "logfile" /**< Location of our logfile. */
49 #define ARG_LOGLEVEL "loglevel" /**< Log verbosity. */
50 #define ARG_READ_PASSWORD_FROM_STDIN \
51  "read-password-from-stdin" /**< Read password from stdin. */
52 
53 /* Static member variables */
54 QMap<QString, QString> Vidalia::_args; /**< List of command-line arguments. */
55 QString Vidalia::_style; /**< The current GUI style. */
56 QString Vidalia::_language; /**< The current language. */
57 TorControl* Vidalia::_torControl = 0; /**< Main TorControl object. */
59 QList<QTranslator *> Vidalia::_translators;
60 
61 
62 /** Catches debugging messages from Qt and sends them to Vidalia's logs. If Qt
63  * emits a QtFatalMsg, we will write the message to the log and then abort().
64  */
65 void
66 Vidalia::qt_msg_handler(QtMsgType type, const char *s)
67 {
68  QString msg(s);
69  switch (type) {
70  case QtDebugMsg:
71  vDebug("QtDebugMsg: %1").arg(msg);
72  break;
73  case QtWarningMsg:
74  vNotice("QtWarningMsg: %1").arg(msg);
75  break;
76  case QtCriticalMsg:
77  vWarn("QtCriticalMsg: %1").arg(msg);
78  break;
79  case QtFatalMsg:
80  vError("QtFatalMsg: %1").arg(msg);
81  break;
82  }
83  if (type == QtFatalMsg) {
84  vError("Fatal Qt error. Aborting.");
85  abort();
86  }
87 }
88 
89 /** Constructor. Parses the command-line arguments, resets Vidalia's
90  * configuration (if requested), and sets up the GUI style and language
91  * translation. */
92 Vidalia::Vidalia(QStringList args, int &argc, char **argv)
93 : QApplication(argc, argv)
94 {
95  qInstallMsgHandler(qt_msg_handler);
96 
97  /* Read in all our command-line arguments. */
98  parseArguments(args);
99 
100  /* Check if we're supposed to reset our config before proceeding. */
101  if (_args.contains(ARG_RESET))
103 
104  /* See if we should load a default configuration file. */
107 
108  /* Handle the -loglevel and -logfile options. */
109  if (_args.contains(ARG_LOGFILE))
110  _log.open(_args.value(ARG_LOGFILE));
111  if (_args.contains(ARG_LOGLEVEL)) {
113  _args.value(ARG_LOGLEVEL)));
114  if (!_args.contains(ARG_LOGFILE))
115  _log.open(stdout);
116  }
117  if (!_args.contains(ARG_LOGLEVEL) &&
118  !_args.contains(ARG_LOGFILE))
120 
121  /* Translate the GUI to the appropriate language. */
123  /* Set the GUI style appropriately. */
124  setStyle(_args.value(ARG_GUISTYLE));
125 
126  /* Creates a TorControl object, used to talk to Tor. */
127  _torControl = new TorControl(TorSettings().getControlMethod());
128 
129 #ifdef USE_MARBLE
130  /* Tell Marble where to stash its generated data */
131  Marble::MarbleDirs::setMarbleDataPath(dataDirectory());
132 
133 #ifdef Q_OS_WIN32
134  Marble::MarbleDirs::setMarblePluginPath(vApp->applicationDirPath()
135  + "/plugins/marble");
136 #endif
137 #endif
138 #ifdef Q_WS_MAC
139  setStyleSheet("QTreeWidget { font-size: 12pt }");
140 #endif
141 }
142 
143 /** Destructor */
145 {
146  delete _torControl;
147 }
148 
149 /** Enters the main event loop and waits until exit() is called. The signal
150  * running() will be emitted when the event loop has started. */
151 int
153 {
154  QTimer::singleShot(0, vApp, SLOT(onEventLoopStarted()));
155  return vApp->exec();
156 }
157 
158 /** Called when the application's main event loop has started. This method
159  * will emit the running() signal to indicate that the application's event
160  * loop is running. */
161 void
163 {
164  emit running();
165 }
166 
167 #if defined(Q_OS_WIN)
168 /** On Windows, we need to catch the WM_QUERYENDSESSION message
169  * so we know that it is time to shutdown. */
170 bool
171 Vidalia::winEventFilter(MSG *msg, long *result)
172 {
173  if (msg->message == WM_QUERYENDSESSION) {
174  quit();
175  }
176  return QApplication::winEventFilter(msg, result);
177 }
178 #endif
179 
180 /** Returns true if the user wants to see usage information. */
181 bool
183 {
184  return _args.contains(ARG_HELP);
185 }
186 
187 /** Displays usage information for command-line args. */
188 void
190 {
191  QString usage;
192  QTextStream out(&usage);
193 
194  out << "Available Options:" << endl;
195  out << "<table>";
196  out << trow(tcol("-"ARG_HELP) +
197  tcol(tr("Displays this usage message and exits.")));
198  out << trow(tcol("-"ARG_RESET) +
199  tcol(tr("Resets ALL stored Vidalia settings.")));
200  out << trow(tcol("-"ARG_DATADIR" &lt;dir&gt;") +
201  tcol(tr("Sets the directory Vidalia uses for data files.")));
202  out << trow(tcol("-"ARG_PIDFILE" &lt;file&gt;") +
203  tcol(tr("Sets the name and location of Vidalia's pidfile.")));
204  out << trow(tcol("-"ARG_LOGFILE" &lt;file&gt;") +
205  tcol(tr("Sets the name and location of Vidalia's logfile.")));
206  out << trow(tcol("-"ARG_LOGLEVEL" &lt;level&gt;") +
207  tcol(tr("Sets the verbosity of Vidalia's logging.") +
208  "<br>[" + Log::logLevels().join("|") +"]"));
209  out << trow(tcol("-"ARG_GUISTYLE" &lt;style&gt;") +
210  tcol(tr("Sets Vidalia's interface style.") +
211  "<br>[" + QStyleFactory::keys().join("|") + "]"));
212  out << trow(tcol("-"ARG_LANGUAGE" &lt;language&gt;") +
213  tcol(tr("Sets Vidalia's language.") +
214  "<br>[" + LanguageSupport::languageCodes().join("|") + "]"));
215  out << "</table>";
216 
218  tr("Vidalia Usage Information"), usage, VMessageBox::Ok);
219 }
220 
221 /** Returns true if the specified argument expects a value. */
222 bool
223 Vidalia::argNeedsValue(QString argName)
224 {
225  return (argName == ARG_GUISTYLE ||
226  argName == ARG_LANGUAGE ||
227  argName == ARG_DATADIR ||
228  argName == ARG_PIDFILE ||
229  argName == ARG_LOGFILE ||
230  argName == ARG_LOGLEVEL);
231 }
232 
233 /** Parses the list of command-line arguments for their argument names and
234  * values. */
235 void
236 Vidalia::parseArguments(QStringList args)
237 {
238  QString arg, value;
239 
240  /* Loop through all command-line args/values and put them in a map */
241  for (int i = 0; i < args.size(); i++) {
242  /* Get the argument name and set a blank value */
243  arg = args.at(i).toLower();
244  value = "";
245 
246  /* Check if it starts with a - or -- */
247  if (arg.startsWith("-")) {
248  arg = arg.mid((arg.startsWith("--") ? 2 : 1));
249  }
250  /* Argument names do not include equal sign. Assume value follows. */
251  if (arg.indexOf("=") > -1) {
252  value = arg.right(arg.length() - (arg.indexOf("=")+1));
253  arg = arg.left(arg.indexOf("="));
254  }
255  else
256  /* Check if it takes a value and there is one on the command-line */
257  if (i < args.size()-1 && argNeedsValue(arg)) {
258  value = args.at(++i);
259  }
260  /* Place this arg/value in the map */
261  _args.insert(arg, value);
262  }
263 }
264 
265 /** Verifies that all specified arguments were valid. */
266 bool
268 {
269  /* Check for missing parameter values */
270  QMapIterator<QString, QString> _i(_args);
271  QString tmp;
272  while(_i.hasNext()) {
273  _i.next();
274  if(argNeedsValue(_i.key()) && (_i.value() == "")) {
275  errmsg = tr("Value required for parameter :") + _i.key();
276  return false;
277  }
278  }
279  /* Check for a language that Vidalia recognizes. */
280  if (_args.contains(ARG_LANGUAGE) &&
282  errmsg = tr("Invalid language code specified: ") + _args.value(ARG_LANGUAGE);
283  return false;
284  }
285  /* Check for a valid GUI style */
286  if (_args.contains(ARG_GUISTYLE) &&
287  !QStyleFactory::keys().contains(_args.value(ARG_GUISTYLE),
288  Qt::CaseInsensitive)) {
289  errmsg = tr("Invalid GUI style specified: ") + _args.value(ARG_GUISTYLE);
290  return false;
291  }
292  /* Check for a valid log level */
293  if (_args.contains(ARG_LOGLEVEL) &&
294  !Log::logLevels().contains(_args.value(ARG_LOGLEVEL))) {
295  errmsg = tr("Invalid log level specified: ") + _args.value(ARG_LOGLEVEL);
296  return false;
297  }
298  /* Check for a writable log file */
299  if (_args.contains(ARG_LOGFILE) && !_log.isOpen()) {
300  errmsg = tr("Unable to open log file '%1': %2")
301  .arg(_args.value(ARG_LOGFILE))
302  .arg(_log.errorString());
303  return false;
304  }
305  return true;
306 }
307 
308 /** Sets the translation Vidalia will use. If one was specified on the
309  * command-line, we will use that. Otherwise, we'll check to see if one was
310  * saved previously. If not, we'll default to one appropriate for the system
311  * locale. */
312 bool
313 Vidalia::setLanguage(QString languageCode)
314 {
315  /* If the language code is empty, use the previously-saved setting */
316  if (languageCode.isEmpty()) {
317  VidaliaSettings settings;
318  languageCode = settings.getLanguageCode();
319  }
320  /* Translate into the desired langauge */
321  if (retranslateUi(languageCode)) {
322  _language = languageCode;
323  return true;
324  }
325  return false;
326 }
327 
328 /** Sets the GUI style Vidalia will use. If one was specified on the
329  * command-line, we will use that. Otherwise, we'll check to see if one was
330  * saved previously. If not, we'll default to one appropriate for the
331  * operating system. */
332 bool
333 Vidalia::setStyle(QString styleKey)
334 {
335  /* If no style was specified, use the previously-saved setting */
336  if (styleKey.isEmpty()) {
337  VidaliaSettings settings;
338  styleKey = settings.getInterfaceStyle();
339  }
340  /* Apply the specified GUI style */
341  if (QApplication::setStyle(styleKey)) {
342  _style = styleKey;
343  return true;
344  }
345  return false;
346 }
347 
348 /** Returns the directory Vidalia uses for its data files. */
349 QString
351 {
352  if (_args.contains(ARG_DATADIR)) {
353  return _args.value(ARG_DATADIR);
354  }
355  return defaultDataDirectory();
356 }
357 
358 /** Returns the default location of Vidalia's data directory. */
359 QString
361 {
362 #if defined(Q_OS_WIN32)
363  return (win32_app_data_folder() + "\\Vidalia");
364 #elif defined(Q_OS_MAC)
365  return (QDir::homePath() + "/Library/Vidalia");
366 #else
367  return (QDir::homePath() + "/.vidalia");
368 #endif
369 }
370 
371 /** Returns the location of Vidalia's pid file. */
372 QString
374 {
375  if (_args.contains(ARG_PIDFILE)) {
376  return _args.value(ARG_PIDFILE);
377  }
378  return QDir::convertSeparators("/var/run/tor/tor.pid");
379 }
380 
381 bool
383 {
384  return _args.contains(ARG_READ_PASSWORD_FROM_STDIN);
385 }
386 
387 /** Writes <b>msg</b> with severity <b>level</b> to Vidalia's log. */
389 Vidalia::log(Log::LogLevel level, QString msg)
390 {
391  return _log.log(level, msg);
392 }
393 
394 /** Creates and binds a shortcut such that when <b>key</b> is pressed in
395  * <b>sender</b>'s context, <b>receiver</b>'s <b>slot</b> will be called. */
396 void
397 Vidalia::createShortcut(const QKeySequence &key, QWidget *sender,
398  QObject *receiver, const char *slot)
399 {
400  QShortcut *s = new QShortcut(key, sender);
401  connect(s, SIGNAL(activated()), receiver, slot);
402 }
403 
404 /** Creates and binds a shortcut such that when <b>key</b> is pressed in
405  * <b>sender</b>'s context, <b>receiver</b>'s <b>slot</b> will be called. */
406 void
407 Vidalia::createShortcut(const QString &key, QWidget *sender,
408  QObject *receiver, const char *slot)
409 {
410  createShortcut(QKeySequence(key), sender, receiver, slot);
411 }
412 
413 void
415 {
416  vInfo("Removing all currently installed UI translator objects.");
417  foreach (QTranslator *translator, _translators) {
418  QApplication::removeTranslator(translator);
419  delete translator;
420  }
421  _translators.clear();
422 }
423 
424 bool
425 Vidalia::retranslateUi(const QString &languageCode)
426 {
427  QTranslator *systemQtTranslator = 0;
428  QTranslator *vidaliaQtTranslator = 0;
429  QTranslator *vidaliaTranslator = 0;
430 
431  if (! LanguageSupport::isValidLanguageCode(languageCode)) {
432  vWarn("Invalid language code: %1").arg(languageCode);
433  return false;
434  }
435  if (! languageCode.compare("en", Qt::CaseInsensitive)) {
436  vNotice("Resetting UI translation to English default.");
437  _language = languageCode;
439  return true;
440  }
441 
442  systemQtTranslator = new QTranslator(vApp);
443  Q_CHECK_PTR(systemQtTranslator);
444  QString qtDir = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
445  systemQtTranslator->load(qtDir + "/qt_" + languageCode + ".qm");
446 
447 
448  vidaliaQtTranslator = new QTranslator(vApp);
449  Q_CHECK_PTR(vidaliaQtTranslator);
450  vidaliaQtTranslator->load(":/lang/qt_" + languageCode + ".qm");
451 
452  vidaliaTranslator = new QTranslator(vApp);
453  Q_CHECK_PTR(vidaliaTranslator);
454  if (! vidaliaTranslator->load(":/lang/vidalia_" + languageCode + ".qm"))
455  goto err;
456 
458  vNotice("Changing UI translation from '%1' to '%2'").arg(_language)
459  .arg(languageCode);
460  _language = languageCode;
461  QApplication::installTranslator(systemQtTranslator);
462  QApplication::installTranslator(vidaliaQtTranslator);
463  QApplication::installTranslator(vidaliaTranslator);
464  _translators << systemQtTranslator
465  << vidaliaQtTranslator
466  << vidaliaTranslator;
467 
468  return true;
469 
470 err:
471  vWarn("Unable to set UI translation to '%1'").arg(languageCode);
472  if (systemQtTranslator)
473  delete systemQtTranslator;
474  if (vidaliaQtTranslator)
475  delete vidaliaQtTranslator;
476  if (vidaliaTranslator)
477  delete vidaliaTranslator;
478  return false;
479 }
480 
481 /** Copies a default settings file (if one exists) to Vidalia's data
482  * directory. */
483 void
485 {
486 #ifdef Q_OS_MACX
487  CFURLRef confUrlRef;
488  CFStringRef pathRef;
489  const char *path;
490 
491  confUrlRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(),
492  CFSTR("vidalia"), CFSTR("conf"), NULL);
493  if (confUrlRef == NULL)
494  return;
495 
496  pathRef = CFURLCopyFileSystemPath(confUrlRef, kCFURLPOSIXPathStyle);
497  path = CFStringGetCStringPtr(pathRef, CFStringGetSystemEncoding());
498 
499  if (path) {
500  QString defaultConfFile = QString::fromLocal8Bit(path);
501  QFileInfo fi(defaultConfFile);
502  if (fi.exists()) {
503  QFileInfo out(VidaliaSettings::settingsFile());
504  if (! out.dir().exists())
505  out.dir().mkpath(".");
506  QFile::copy(defaultConfFile, out.absoluteFilePath());
507  }
508  }
509  CFRelease(confUrlRef);
510  CFRelease(pathRef);
511 #endif
512 }
513