mainwindow.cpp

Go to the documentation of this file.
00001 /****************************************************************
00002  *  Vidalia is distributed under the following license:
00003  *
00004  *  Copyright (C) 2006-2007,  Matt Edman, Justin Hipple
00005  *
00006  *  This program is free software; you can redistribute it and/or
00007  *  modify it under the terms of the GNU General Public License
00008  *  as published by the Free Software Foundation; either version 2
00009  *  of the License, or (at your option) any later version.
00010  *
00011  *  This program is distributed in the hope that it will be useful,
00012  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00013  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00014  *  GNU General Public License for more details.
00015  *
00016  *  You should have received a copy of the GNU General Public License
00017  *  along with this program; if not, write to the Free Software
00018  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, 
00019  *  Boston, MA  02110-1301, USA.
00020  ****************************************************************/
00021 
00022 /** 
00023  * \file mainwindow.cpp
00024  * \version $Id: mainwindow.cpp 1877 2007-08-26 03:09:22Z edmanm $
00025  * \brief Main (hidden) window. Creates tray menu and child windows
00026  *
00027  * Implements the main window. The main window is a hidden window that serves
00028  * as the parent of the tray icon and popup menu, as well as other application
00029  * dialogs.
00030  */
00031 
00032 #include <QtGui>
00033 #include <QTimer>
00034 #include <vidalia.h>
00035 #include <util/file.h>
00036 #include <util/html.h>
00037 #include <util/string.h>
00038 #include <QSysInfo>
00039 
00040 #include "common/vmessagebox.h"
00041 #include "common/animatedpixmap.h"
00042 #include "mainwindow.h"
00043 
00044 
00045 #define IMG_APP_ICON       ":/images/16x16/tor-logo.png"
00046 #define IMG_BWGRAPH        ":/images/16x16/utilities-system-monitor.png"
00047 #define IMG_CONTROL_PANEL  ":/images/16x16/preferences-desktop.png"
00048 #define IMG_MESSAGELOG     ":/images/16x16/format-justify-fill.png"
00049 #define IMG_CONFIG         ":/images/16x16/preferences-system.png"
00050 #define IMG_IDENTITY       ":/images/16x16/system-users.png"
00051 #define IMG_HELP           ":/images/16x16/help-browser.png"
00052 #define IMG_ABOUT          ":/images/16x16/tor-logo.png"
00053 #define IMG_EXIT           ":/images/16x16/emblem-unreadable.png"
00054 #define IMG_NETWORK        ":/images/16x16/applications-internet.png"
00055 
00056 #define IMG_START_TOR_16     ":/images/16x16/start-tor.png"
00057 #define IMG_STOP_TOR_16      ":/images/16x16/stop-tor.png"
00058 #define IMG_START_TOR_48     ":/images/48x48/start-tor.png"
00059 #define IMG_STOP_TOR_48      ":/images/48x48/stop-tor.png"
00060 #define IMG_TOR_STOPPED_48   ":/images/48x48/tor-off.png"
00061 #define IMG_TOR_RUNNING_48   ":/images/48x48/tor-on.png"
00062 #define IMG_TOR_STARTING_48  ":/images/48x48/tor-starting.png"
00063 #define IMG_TOR_STOPPING_48  ":/images/48x48/tor-stopping.png"
00064 
00065 #define ANIM_PROCESS_WORKING  ":/images/32x32/process-working.png"
00066 
00067 /* Decide which of our four sets of tray icons to use. */
00068 #if defined(USE_QSYSTEMTRAYICON)
00069 /* QSystemTrayIcon is available */
00070 #if defined(Q_WS_WIN)
00071 /* QSystemTrayIcon on Windows wants 16x16 .png files */
00072 #define IMG_TOR_STOPPED  ":/images/16x16/tor-off.png"
00073 #define IMG_TOR_RUNNING  ":/images/16x16/tor-on.png"
00074 #define IMG_TOR_STARTING ":/images/16x16/tor-starting.png"
00075 #define IMG_TOR_STOPPING ":/images/16x16/tor-stopping.png"
00076 #endif
00077 #else
00078 /* No QSystemTrayIcon support */
00079 #if defined(Q_WS_WIN)
00080 /* Use the .ico files */
00081 #include "res/vidalia_win.rc.h"
00082 #define IMG_TOR_STOPPED    QString::number(IDI_TOR_OFF)
00083 #define IMG_TOR_RUNNING    QString::number(IDI_TOR_ON)
00084 #define IMG_TOR_STARTING   QString::number(IDI_TOR_STARTING)
00085 #define IMG_TOR_STOPPING   QString::number(IDI_TOR_STOPPING)
00086 #endif
00087 #endif
00088 #if defined(Q_WS_MAC)
00089 /* On Mac, we always go straight to Carbon to load our dock images 
00090  * from .icns files */
00091 #define IMG_TOR_STOPPED    "tor-off"
00092 #define IMG_TOR_RUNNING    "tor-on"
00093 #define IMG_TOR_STARTING   "tor-starting"
00094 #define IMG_TOR_STOPPING   "tor-stopping"
00095 #elif defined(Q_WS_X11)
00096 /* On X11, we just use always the 22x22 .png files */
00097 #define IMG_TOR_STOPPED    ":/images/22x22/tor-off.png"
00098 #define IMG_TOR_RUNNING    ":/images/22x22/tor-on.png"
00099 #define IMG_TOR_STARTING   ":/images/22x22/tor-starting.png"
00100 #define IMG_TOR_STOPPING   ":/images/22x22/tor-stopping.png"
00101 #endif
00102 
00103 /** Only allow 'New Identity' to be clicked once every 10 seconds. */
00104 #define MIN_NEWIDENTITY_INTERVAL   (10*1000)
00105 
00106 
00107 /** Default constructor. It installs an icon in the system tray area and
00108  * creates the popup menu associated with that icon. */
00109 MainWindow::MainWindow()
00110 : VidaliaWindow("MainWindow")
00111 {
00112   VidaliaSettings settings;
00113 
00114   ui.setupUi(this);
00115 
00116   /* Create all the dialogs of which we only want one instance */
00117   _messageLog = new MessageLog();
00118   _bandwidthGraph = new BandwidthGraph();
00119   _netViewer = new NetViewer();
00120   
00121   /* Create the actions that will go in the tray menu */
00122   createActions();
00123   /* Creates a tray icon with a context menu and adds it to the system's
00124    * notification area. */
00125   createTrayIcon();
00126   /* Start with Tor initially stopped */
00127   _status = Unset;
00128   updateTorStatus(Stopped);
00129   
00130   /* Create a new TorControl object, used to communicate with and manipulate Tor */
00131   _torControl = Vidalia::torControl(); 
00132   connect(_torControl, SIGNAL(started()), this, SLOT(started()));
00133   connect(_torControl, SIGNAL(startFailed(QString)),
00134                  this,   SLOT(startFailed(QString)));
00135   connect(_torControl, SIGNAL(stopped(int, QProcess::ExitStatus)),
00136                  this,   SLOT(stopped(int, QProcess::ExitStatus)));
00137   connect(_torControl, SIGNAL(connected()), this, SLOT(connected()));
00138   connect(_torControl, SIGNAL(disconnected()), this, SLOT(disconnected()));
00139   connect(_torControl, SIGNAL(connectFailed(QString)), 
00140                  this,   SLOT(connectFailed(QString)));
00141   connect(_torControl, SIGNAL(authenticated()), this, SLOT(authenticated()));
00142   connect(_torControl, SIGNAL(authenticationFailed(QString)),
00143                  this,   SLOT(authenticationFailed(QString)));
00144 
00145   /* Make sure we shut down when the operating system is restarting */
00146   connect(vApp, SIGNAL(shutdown()), this, SLOT(shutdown()));
00147 
00148   if (_torControl->isRunning()) {
00149     /* Tor may be already running, but we still need to connect to it. So,
00150      * update our status now. */ 
00151     updateTorStatus(Starting);
00152     /* Tor was already running */
00153     started();
00154   } else if (settings.runTorAtStart()) {
00155     /* If we're supposed to start Tor when Vidalia starts, then do it now */
00156     start();
00157   }
00158   
00159   if (isTrayIconSupported()) {
00160     /* Make the tray icon visible */
00161     _trayIcon.show();
00162     /* Check if we are supposed to show our main window on startup */
00163     ui.chkShowOnStartup->setChecked(settings.showMainWindowAtStart());
00164     if (ui.chkShowOnStartup->isChecked())
00165       show();
00166   } else {
00167     /* Don't let people hide the main window, since that's all they have. */
00168     ui.chkShowOnStartup->hide();
00169     ui.btnHide->hide();
00170     setMinimumHeight(height()-ui.btnHide->height());
00171     setMaximumHeight(height()-ui.btnHide->height());
00172     show();
00173   }
00174 }
00175 
00176 /** Destructor. */
00177 MainWindow::~MainWindow()
00178 {
00179   _trayIcon.hide();
00180   delete _messageLog;
00181   delete _netViewer;
00182   delete _bandwidthGraph;
00183 }
00184 
00185 /** Returns true if we're running on a platform with tray icon support. */
00186 bool
00187 MainWindow::isTrayIconSupported()
00188 {
00189 #if defined(Q_WS_WIN) || defined(Q_WS_MAC)
00190   /* We always have a tray on Win32 or a dock on OS X */
00191   return true;
00192 #elif defined(USE_QSYSTEMTRAYICON)
00193   /* Ask Qt if there is a tray available */
00194   return QSystemTrayIcon::isSystemTrayAvailable();  
00195 #else
00196   /* XXX: This is too optimistic, but we need to make our own tray icon
00197    * implementation smart enough to detect a system tray on X11. */
00198   return true;
00199 #endif
00200 }
00201 
00202 /** Terminate the Tor process if it is being run under Vidalia, disconnect all
00203  * TorControl signals, and exit Vidalia. */
00204 void
00205 MainWindow::shutdown()
00206 {
00207   if (_torControl->isVidaliaRunningTor()) {
00208     /* Kill our Tor process now */ 
00209     _torControl->stop();
00210   }
00211 
00212   /* Disconnect all of the TorControl object's signals */
00213   QObject::disconnect(_torControl, 0, 0, 0);
00214 
00215   /* And then quit for real */
00216   QCoreApplication::quit();
00217 }
00218 
00219 /** Called when the application is closing, by selecting "Exit" from the tray
00220  * menu. If we're running a Tor server, then ask if we want to kill Tor now,
00221  * or do a delayed shutdown. */
00222 void
00223 MainWindow::close()
00224 {
00225   if (_torControl->isVidaliaRunningTor()) {
00226     /* If we're running a server currently, ask if we want to do a delayed
00227      * shutdown. If we do, then close Vidalia only when Tor stops. Otherwise,
00228      * kill Tor and bail now. */
00229     ServerSettings settings(_torControl);
00230     if (_torControl->isConnected() && settings.isServerEnabled()) {
00231       connect(_torControl, SIGNAL(stopped()), this, SLOT(shutdown()));
00232       if (!stop())
00233         QObject::disconnect(_torControl, SIGNAL(stopped()), this, SLOT(shutdown()));
00234       return;
00235     }
00236   }
00237   /* Shut down Tor (if necessary) and exit Vidalia */
00238   shutdown();
00239 }
00240 
00241 /** Create and bind actions to events. Setup for initial
00242  * tray menu configuration. */
00243 void 
00244 MainWindow::createActions()
00245 {
00246   _startStopAct = new QAction(QIcon(IMG_START_TOR_16), tr("Start Tor"), this);
00247   connect(_startStopAct, SIGNAL(triggered()), this, SLOT(start()));
00248 
00249   _exitAct = new QAction(QIcon(IMG_EXIT), tr("Exit"), this);
00250   connect(_exitAct, SIGNAL(triggered()), this, SLOT(close()));
00251 
00252   _bandwidthAct = new QAction(QIcon(IMG_BWGRAPH), tr("Bandwidth Graph"), this);
00253   connect(_bandwidthAct, SIGNAL(triggered()), 
00254           _bandwidthGraph, SLOT(showWindow()));
00255   connect(ui.lblBandwidthGraph, SIGNAL(clicked()),
00256           _bandwidthGraph, SLOT(showWindow()));
00257 
00258   _messageAct = new QAction(QIcon(IMG_MESSAGELOG), tr("Message Log"), this);
00259   connect(_messageAct, SIGNAL(triggered()),
00260           _messageLog, SLOT(showWindow()));
00261   connect(ui.lblMessageLog, SIGNAL(clicked()),
00262           _messageLog, SLOT(showWindow()));
00263 
00264   _networkAct = new QAction(QIcon(IMG_NETWORK), tr("Network Map"), this);
00265   connect(_networkAct, SIGNAL(triggered()), 
00266           _netViewer, SLOT(showWindow()));
00267   connect(ui.lblViewNetwork, SIGNAL(clicked()),
00268           _netViewer, SLOT(showWindow()));
00269 
00270   _controlPanelAct = new QAction(QIcon(IMG_CONTROL_PANEL), 
00271                                  tr("Control Panel"), this);
00272   connect(_controlPanelAct, SIGNAL(triggered()), this, SLOT(show()));
00273 
00274   _configAct = new QAction(QIcon(IMG_CONFIG), tr("Settings"), this);
00275   connect(_configAct, SIGNAL(triggered()), this, SLOT(showConfigDialog()));
00276   
00277   _aboutAct = new QAction(QIcon(IMG_ABOUT), tr("About"), this);
00278   connect(_aboutAct, SIGNAL(triggered()), this, SLOT(showAboutDialog()));
00279 
00280   _helpAct = new QAction(QIcon(IMG_HELP), tr("Help"), this);
00281   connect(_helpAct, SIGNAL(triggered()), vApp, SLOT(help()));
00282   connect(ui.lblHelpBrowser, SIGNAL(clicked()), vApp, SLOT(help()));
00283 
00284   _newIdentityAct = new QAction(QIcon(IMG_IDENTITY), tr("New Identity"), this);
00285   _newIdentityAct->setEnabled(false);
00286   connect(_newIdentityAct, SIGNAL(triggered()), this, SLOT(newIdentity()));
00287 }
00288 
00289 /** Creates a tray icon with a context menu and adds it to the system
00290  * notification area. On Mac, we also set up an application menubar. */
00291 void
00292 MainWindow::createTrayIcon()
00293 {
00294   /* Create the default menu bar (Mac) */
00295   createMenuBar();
00296   /* Create a tray menu and add it to the tray icon */
00297   _trayIcon.setContextMenu(createTrayMenu());
00298 
00299 #if defined(USE_QSYSTEMTRAYICON)
00300   connect(&_trayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
00301                 this, SLOT(trayActivated(QSystemTrayIcon::ActivationReason)));
00302 #else
00303   connect(&_trayIcon, SIGNAL(doubleClicked()), this, SLOT(show()));
00304 #endif
00305 }
00306 
00307 /** Creates a QMenu object that contains QActions which compose the system 
00308  * tray menu. */
00309 QMenu* 
00310 MainWindow::createTrayMenu()
00311 {
00312   QMenu *menu = new QMenu(this);
00313   menu->addAction(_startStopAct);
00314   menu->addSeparator();
00315   menu->addAction(_bandwidthAct);
00316   menu->addAction(_messageAct);
00317   menu->addAction(_networkAct);
00318   menu->addAction(_newIdentityAct);
00319   menu->addSeparator();
00320   menu->addAction(_controlPanelAct);
00321   
00322 #if !defined(Q_WS_MAC)
00323   /* These aren't added to the dock menu on Mac, since they are in the
00324    * standard Mac locations in the menu bar. */
00325   menu->addAction(_configAct);
00326   menu->addAction(_helpAct);
00327   menu->addAction(_aboutAct);
00328   menu->addSeparator();
00329   menu->addAction(_exitAct);
00330 #endif
00331   return menu;
00332 }
00333 
00334 /** Creates a new menubar with no parent, so Qt will use this as the "default
00335  * menubar" on Mac. This adds on to the existing actions from the createMens()
00336  * method. */
00337 void
00338 MainWindow::createMenuBar()
00339 {
00340 #if defined(Q_WS_MAC)
00341   /* Mac users sure like their shortcuts. Actions NOT mentioned below
00342    * don't explicitly need shortcuts, since they are merged to the default
00343    * menubar and get the default shortcuts anyway. */
00344   _startStopAct->setShortcut(tr("Ctrl+T"));
00345   _bandwidthAct->setShortcut(tr("Ctrl+B"));
00346   _messageAct->setShortcut(tr("Ctrl+L"));
00347   _networkAct->setShortcut(tr("Ctrl+N"));
00348   _helpAct->setShortcut(tr("Ctrl+?"));
00349   _newIdentityAct->setShortcut(tr("Ctrl+I"));
00350   _controlPanelAct->setShortcut(tr("Ctrl+P"));
00351 
00352   /* Force Qt to put merge the Exit, Configure, and About menubar options into
00353    * the default menu, even if Vidalia is currently not speaking English. */
00354   _exitAct->setText("exit");
00355   _configAct->setText("config");
00356   _aboutAct->setText("about");
00357   
00358   /* The File, Help, and Configure menus will get merged into the application
00359    * menu by Qt. */
00360   QMenuBar *menuBar = new QMenuBar();
00361   QMenu *fileMenu = menuBar->addMenu(tr("File"));
00362   fileMenu->addAction(_exitAct);
00363   
00364   QMenu *torMenu = menuBar->addMenu(tr("Tor"));
00365   torMenu->addAction(_startStopAct);
00366   torMenu->addSeparator();
00367   torMenu->addAction(_newIdentityAct);
00368 
00369   QMenu *viewMenu = menuBar->addMenu(tr("View"));
00370   viewMenu->addAction(_controlPanelAct);
00371   viewMenu->addSeparator();
00372   viewMenu->addAction(_bandwidthAct);
00373   viewMenu->addAction(_messageAct);
00374   viewMenu->addAction(_networkAct);
00375   viewMenu->addAction(_configAct);
00376   
00377   QMenu *helpMenu = menuBar->addMenu(tr("Help"));
00378   _helpAct->setText(tr("Vidalia Help"));
00379   helpMenu->addAction(_helpAct);
00380   helpMenu->addAction(_aboutAct);
00381   
00382   setMenuBar(menuBar);
00383 #endif
00384 }
00385 
00386 /** Updates the UI to reflect Tor's current <b>status</b>. Returns the
00387  * previously set TorStatus value.*/
00388 MainWindow::TorStatus
00389 MainWindow::updateTorStatus(TorStatus status)
00390 {
00391   QString statusText, actionText;
00392   QString trayIconFile, statusIconFile;
00393   TorStatus prevStatus = _status;
00394  
00395   vNotice("Tor status changed from '%1' to '%2'.")
00396     .arg(toString(prevStatus)).arg(toString(status));
00397   _status = status;
00398 
00399   if (status == Stopped) {
00400       statusText = tr("Tor is not running");
00401       actionText = tr("Start Tor");
00402       trayIconFile = IMG_TOR_STOPPED;
00403       statusIconFile = IMG_TOR_STOPPED_48;
00404       _startStopAct->setEnabled(true);
00405       _startStopAct->setText(actionText);
00406       _startStopAct->setIcon(QIcon(IMG_START_TOR_16));
00407       ui.lblStartStopTor->setEnabled(true);
00408       ui.lblStartStopTor->setText(actionText);
00409       ui.lblStartStopTor->setPixmap(QPixmap(IMG_START_TOR_48));
00410       ui.lblStartStopTor->setStatusTip(actionText);
00411 
00412       /* XXX: This might need to be smarter if we ever start connecting other
00413        * slots to these triggered() and clicked() signals. */
00414       QObject::disconnect(_startStopAct, SIGNAL(triggered()), this, 0);
00415       QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
00416       connect(_startStopAct, SIGNAL(triggered()), this, SLOT(start()));
00417       connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(start()));
00418   } else if (status == Stopping) {
00419       if (_delayedShutdownStarted) {
00420         statusText = tr("Your Tor server is shutting down.\r\n" 
00421                         "Click 'Stop Tor' again to force Tor to stop now.");
00422       } else {
00423         statusText = tr("Tor is shutting down");
00424       }
00425       trayIconFile = IMG_TOR_STOPPING;
00426       statusIconFile = IMG_TOR_STOPPING_48;
00427       
00428       ui.lblStartStopTor->setStatusTip(tr("Stop Tor Now"));
00429   } else if (status == Started) {
00430       statusText = tr("Tor is running");
00431       actionText = tr("Stop Tor");
00432       _startStopAct->setEnabled(true);
00433       _startStopAct->setText(actionText);
00434       _startStopAct->setIcon(QIcon(IMG_STOP_TOR_16));
00435       ui.lblStartStopTor->setEnabled(true);
00436       ui.lblStartStopTor->setText(actionText);
00437       ui.lblStartStopTor->setPixmap(QPixmap(IMG_STOP_TOR_48));
00438       ui.lblStartStopTor->setStatusTip(actionText);
00439       
00440       /* XXX: This might need to be smarter if we ever start connecting other
00441        * slots to these triggered() and clicked() signals. */
00442       QObject::disconnect(_startStopAct, SIGNAL(triggered()), this, 0);
00443       QObject::disconnect(ui.lblStartStopTor, SIGNAL(clicked()), this, 0);
00444       connect(_startStopAct, SIGNAL(triggered()), this, SLOT(stop()));
00445       connect(ui.lblStartStopTor, SIGNAL(clicked()), this, SLOT(stop()));
00446   } else if (status == Starting)  {
00447       statusText = tr("Tor is starting up");
00448       trayIconFile = IMG_TOR_STARTING;
00449       statusIconFile = IMG_TOR_STARTING_48;
00450       _startStopAct->setEnabled(false);
00451       ui.lblStartStopTor->setText(tr("Starting Tor"));
00452       ui.lblStartStopTor->setEnabled(false);
00453       ui.lblStartStopTor->setStatusTip(statusText);
00454       ui.lblStartStopTor->setAnimation(QPixmap(ANIM_PROCESS_WORKING));
00455   } else if (status == Authenticated) {
00456       trayIconFile = IMG_TOR_RUNNING;
00457       statusIconFile = IMG_TOR_RUNNING_48;
00458   }
00459 
00460   /* Update the tray icon */
00461   if (!trayIconFile.isEmpty()) {
00462 #if defined(USE_QSYSTEMTRAYICON)
00463     _trayIcon.setIcon(QIcon(trayIconFile));
00464 #else
00465     _trayIcon.setIcon(trayIconFile);
00466 #endif
00467   }
00468   /* Update the status banner on the control panel */
00469   if (!statusIconFile.isEmpty())
00470     ui.lblTorStatusImg->setPixmap(QPixmap(statusIconFile));
00471   if (!statusText.isEmpty()) {
00472     _trayIcon.setToolTip(statusText);
00473     ui.lblTorStatus->setText(statusText);
00474   }
00475   return prevStatus;
00476 }
00477 
00478 #if defined(USE_QSYSTEMTRAYICON)
00479 /** Displays the main window if <b>reason</b> is DoubleClick. */
00480 void
00481 MainWindow::trayActivated(QSystemTrayIcon::ActivationReason reason)
00482 {
00483   if (reason == QSystemTrayIcon::DoubleClick)
00484     show();
00485 }
00486 #endif
00487 
00488 /** Called when the "show on startup" checkbox is toggled. */
00489 void
00490 MainWindow::toggleShowOnStartup(bool checked)
00491 {
00492   VidaliaSettings settings;
00493   settings.setShowMainWindowAtStart(checked);
00494 }
00495 
00496 /** Attempts to start Tor. If Tor fails to start, then startFailed() will be
00497  * called with an error message containing the reason. */
00498 void 
00499 MainWindow::start()
00500 {
00501   updateTorStatus(Starting);
00502   /* This doesn't get set to false until Tor is actually up and running, so we
00503    * don't yell at users twice if their Tor doesn't even start, due to the fact
00504    * that QProcess::stopped() is emitted even if the process didn't even
00505    * start. */
00506   _isIntentionalExit = true;
00507   /* Kick off the Tor process */
00508   _torControl->start();
00509 }
00510 
00511 /** Called when the Tor process fails to start, for example, because the path
00512  * specified to the Tor executable didn't lead to an executable. */
00513 void
00514 MainWindow::startFailed(QString errmsg)
00515 {
00516   /* We don't display the error message for now, because the error message
00517    * that Qt gives us in this instance is almost always "Unknown Error". That
00518    * will make users sad. */
00519   Q_UNUSED(errmsg);
00520  
00521   updateTorStatus(Stopped);
00522 
00523   /* Display an error message and see if the user wants some help */
00524   int response = VMessageBox::warning(this, tr("Error Starting Tor"),
00525                    tr("Vidalia was unable to start Tor. Check your settings "
00526                         "to ensure the correct name and location of your Tor "
00527                         "executable is specified."),
00528                    VMessageBox::ShowSettings|VMessageBox::Default,
00529                    VMessageBox::Cancel|VMessageBox::Escape,
00530                    VMessageBox::Help);
00531 
00532   if (response == VMessageBox::ShowSettings) {
00533     /* Show the settings dialog so the user can make sure they're pointing to
00534      * the correct Tor. */
00535      showConfigDialog();
00536   } else if (response == VMessageBox::Help) {
00537     /* Show troubleshooting information about starting Tor */
00538     Vidalia::help("troubleshooting.start");
00539   }
00540 }
00541 
00542 /** Slot: Called when the Tor process is started. It will connect the control
00543  * socket and set the icons and tooltips accordingly. */
00544 void 
00545 MainWindow::started()
00546 {
00547   updateTorStatus(Started);
00548 
00549   /* Now that Tor is running, we want to know if it dies when we didn't want
00550    * it to. */
00551   _isIntentionalExit = false;
00552   /* We haven't started a delayed shutdown yet. */
00553   _delayedShutdownStarted = false;
00554   /* Remember whether we started Tor or not */
00555   _isVidaliaRunningTor = _torControl->isVidaliaRunningTor();
00556   /* Try to connect to Tor's control port */
00557   _torControl->connect();
00558 }
00559 
00560 /** Called when the connection to the control socket fails. The reason will be
00561  * given in the errmsg parameter. */
00562 void
00563 MainWindow::connectFailed(QString errmsg)
00564 {
00565   updateTorStatus(Disconnected);
00566 
00567   /* Ok, ok. It really isn't going to connect. I give up. */
00568   int response = VMessageBox::warning(this, 
00569                    tr("Error Connecting to Tor"), p(errmsg),
00570                    VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, 
00571                    VMessageBox::Retry, VMessageBox::Help);
00572 
00573 
00574   if (response == VMessageBox::Retry) {
00575     /* Let's give it another try. */
00576     _torControl->connect();
00577   } else {
00578     /* Show the help browser (if requested) */
00579     if (response == VMessageBox::Help)
00580       Vidalia::help("troubleshooting.connect");
00581     /* Since Vidalia can't connect, we can't really do much, so stop Tor. */
00582     _torControl->stop();
00583   }
00584 }
00585 
00586 /** Disconnects the control socket and stops the Tor process. */
00587 bool
00588 MainWindow::stop()
00589 {
00590   ServerSettings server(_torControl);
00591   QString errmsg;
00592   TorStatus prevStatus;
00593   bool rc;
00594 
00595   /* If we're running a server, give users the option of terminating
00596    * gracefully so clients have time to find new servers. */
00597   if (server.isServerEnabled() && !_delayedShutdownStarted) {
00598     /* Ask the user if they want to shutdown nicely. */
00599     int response = VMessageBox::question(this, tr("Server is Enabled"),
00600                      tr("You are currently running a Tor server. "
00601                         "Terminating your server will interrupt any "
00602                         "open connections from clients.\n\n"
00603                         "Would you like to shutdown gracefully and "
00604                         "give clients time to find a new server?"),
00605                         VMessageBox::Yes|VMessageBox::Default, 
00606                         VMessageBox::No, 
00607                         VMessageBox::Cancel|VMessageBox::Escape);
00608     if (response == VMessageBox::Yes)
00609       _delayedShutdownStarted = true;
00610     else if (response == VMessageBox::Cancel)
00611       return false;
00612   }
00613   
00614   prevStatus = updateTorStatus(Stopping);  
00615   if (_delayedShutdownStarted) {
00616     /* Start a delayed shutdown */
00617     rc = _torControl->signal(TorSignal::Shutdown, &errmsg);
00618   } else {
00619     /* We want Tor to stop now, regardless of whether we're a server. */
00620     _isIntentionalExit = true;
00621     rc = _torControl->stop(&errmsg);
00622   }
00623   
00624   if (!rc) {
00625     /* We couldn't tell Tor to stop, for some reason. */
00626     int response = VMessageBox::warning(this, tr("Error Stopping Tor"),
00627                      p(tr("Vidalia was unable to stop Tor.")) + p(errmsg),
00628                      VMessageBox::Ok|VMessageBox::Default|VMessageBox::Escape, 
00629                      VMessageBox::Help);
00630       
00631     if (response == VMessageBox::Help) {
00632       /* Show some troubleshooting help */
00633       Vidalia::help("troubleshooting.stop");
00634     }
00635     /* Tor is still running since stopping failed */
00636     _isIntentionalExit = false;
00637     _delayedShutdownStarted = false;
00638     updateTorStatus(prevStatus);
00639   }
00640   return rc;
00641 }
00642 
00643 /** Slot: Called when the Tor process has exited. It will adjust the tray
00644  * icons and tooltips accordingly. */
00645 void 
00646 MainWindow::stopped(int exitCode, QProcess::ExitStatus exitStatus)
00647 {
00648   updateTorStatus(Stopped);
00649 
00650   /* If we didn't intentionally close Tor, then check to see if it crashed or
00651    * if it closed itself and returned an error code. */
00652   if (!_isIntentionalExit) {
00653     /* A quick overview of Tor's code tells me that if it catches a SIGTERM or
00654      * SIGINT, Tor will exit(0). We might need to change this warning message
00655      * if this turns out to not be the case. */
00656     if (exitStatus == QProcess::CrashExit || exitCode != 0) {
00657       int ret = VMessageBox::warning(this, tr("Tor Exited"),
00658                   tr("Vidalia detected that Tor exited unexpectedly.\n\n"
00659                      "Please check the message log for indicators "
00660                      "about what happened to Tor before it exited."),
00661                   VMessageBox::Ok|VMessageBox::Escape, 
00662                   VMessageBox::ShowLog|VMessageBox::Default,
00663                   VMessageBox::Help);
00664       if (ret == VMessageBox::ShowLog)
00665         _messageLog->showWindow();  
00666       else if (ret == VMessageBox::Help)
00667         Vidalia::help("troubleshooting.torexited");
00668     }
00669   }
00670 }
00671 
00672 /** Called when the control socket has successfully connected to Tor. */
00673 void
00674 MainWindow::connected()
00675 {
00676   updateTorStatus(Connected);
00677   authenticate();
00678 }
00679 
00680 /** Called when Vidalia wants to disconnect from a Tor it did not start. */
00681 void
00682 MainWindow::disconnect()
00683 {
00684   updateTorStatus(Disconnecting);
00685   _torControl->disconnect();
00686 }
00687 
00688 /** Called when the control socket has been disconnected. */
00689 void
00690 MainWindow::disconnected()
00691 {
00692   updateTorStatus(Disconnected);
00693   if (!_isVidaliaRunningTor) {
00694     /* If we didn't start our own Tor process, interpret losing the control
00695      * connection as "Tor is stopped". */
00696     updateTorStatus(Stopped);
00697   }
00698   
00699   /*XXX We should warn here if we get disconnected when we didn't intend to */
00700   _newIdentityAct->setEnabled(false);
00701   ui.lblNewIdentity->setEnabled(false);
00702   _isVidaliaRunningTor = false;
00703 }
00704 
00705 /** Attempts to authenticate to Tor's control port, depending on the
00706  * authentication method specified in TorSettings::getAuthenticationMethod().
00707  */
00708 bool
00709 MainWindow::authenticate()
00710 {
00711   TorSettings::AuthenticationMethod authMethod;
00712   TorSettings settings;
00713   ProtocolInfo pi;
00714   
00715   updateTorStatus(Authenticating);
00716   
00717   authMethod = settings.getAuthenticationMethod(); 
00718   pi = _torControl->protocolInfo();
00719   if (!pi.isEmpty()) {
00720     QStringList authMethods = pi.authMethods();
00721     if (authMethods.contains("COOKIE"))
00722       authMethod = TorSettings::CookieAuth;
00723     else if (authMethods.contains("HASHEDPASSWORD"))
00724       authMethod = TorSettings::PasswordAuth;
00725     else if (authMethods.contains("NULL"))
00726       authMethod = TorSettings::NullAuth;
00727   }
00728   
00729   if (authMethod == TorSettings::CookieAuth) {
00730     /* Try to load an auth cookie and send it to Tor */
00731     QByteArray cookie = loadControlCookie(pi.cookieAuthFile());
00732     while (cookie.isEmpty()) {
00733       /* Prompt the user to find their control_auth_cookie */
00734       int ret = VMessageBox::question(this,
00735                   tr("Cookie Authentication Required"),
00736                   p(tr("Tor requires Vidalia to send the contents of an "
00737                        "authentication cookie, but Vidalia was unable to "
00738                        "find one."))
00739                   + p(tr("Would you like to browse for the file "
00740                          "'control_auth_cookie' yourself?")),
00741                 VMessageBox::Browse|VMessageBox::Default,
00742                 VMessageBox::Cancel|VMessageBox::Escape);
00743       
00744       if (ret == VMessageBox::Cancel)
00745         goto cancel;
00746       QString cookieDir = QFileDialog::getOpenFileName(this,
00747                             tr("Tor Data Directory"),
00748                             settings.getDataDirectory(),
00749                             tr("Tor Control Cookie (control_auth_cookie)"));
00750       if (cookieDir.isEmpty())
00751         goto cancel;
00752       cookieDir = QFileInfo(cookieDir).absolutePath();
00753       cookie = loadControlCookie(cookieDir);
00754     }
00755     vNotice("Authenticating using 'cookie' authentication.");
00756     return _torControl->authenticate(cookie);
00757   } else if (authMethod == TorSettings::PasswordAuth) {
00758     /* Get the control password and send it to Tor */
00759     vNotice("Authenticating using 'hashed password' authentication.");
00760     QString password = settings.getControlPassword();
00761     return _torControl->authenticate(password);
00762   }
00763   /* No authentication. Send an empty password. */
00764   vNotice("Authenticating using 'null' authentication.");
00765   return _torControl->authenticate(QString(""));
00766 
00767 cancel:
00768   vWarn("Cancelling control authentication attempt.");
00769   if (_isVidaliaRunningTor)
00770     stop();
00771   else
00772     disconnect();
00773   return false;
00774 }
00775 
00776 /** Called when Vidalia has successfully authenticated to Tor. */
00777 void
00778 MainWindow::authenticated()
00779 {
00780   ServerSettings serverSettings(_torControl);
00781   QString errmsg;
00782 
00783   updateTorStatus(Authenticated);
00784   
00785   /* Let people click on their beloved "New Identity" button */
00786   _newIdentityAct->setEnabled(true);
00787   ui.lblNewIdentity->setEnabled(true);
00788 
00789   /* Register for any pertinent asynchronous events. */
00790   if (!_torControl->setEvents(&errmsg))
00791     VMessageBox::warning(this, tr("Error Registering for Events"),
00792       p(tr("Vidalia was unable to register for Tor events. "
00793            "Many of Vidalia's features may be unavailable."))
00794          + p(errmsg),
00795       VMessageBox::Ok);
00796   
00797   /* If the user changed some of the server's settings while Tor wasn't 
00798    * running, then we better let Tor know about the changes now. */
00799   if (serverSettings.changedSinceLastApply()) {
00800     if (!serverSettings.apply(&errmsg)) {
00801       int ret = VMessageBox::warning(this, 
00802                   tr("Error Applying Server Settings"),
00803                   p(tr("Vidalia was unable to apply your server's settings."))
00804                     + p(errmsg),
00805                   VMessageBox::Ok|VMessageBox::Escape, 
00806                   VMessageBox::ShowSettings|VMessageBox::Default,
00807                   VMessageBox::ShowLog);
00808 
00809       if (ret == VMessageBox::ShowSettings) {
00810         /* Show the config dialog with the server page already shown. */
00811         showConfigDialog(ConfigDialog::Server);
00812       } else if (ret == VMessageBox::ShowLog) {
00813         /* Show the message log. */
00814         _messageLog->showWindow(); 
00815       }
00816     }
00817   }
00818 }
00819 
00820 /** Called when Vidalia fails to authenticate to Tor. The failure reason is
00821  * specified in <b>errmsg</b>. */
00822 void
00823 MainWindow::authenticationFailed(QString errmsg)
00824 {
00825   bool retry = false;
00826   
00827   vWarn("Authentication failed: %1").arg(errmsg);
00828 
00829   /* Parsing log messages is evil, but we're left with little option */
00830   if (errmsg.contains("Password did not match")) {
00831     /* Bad password, so prompt for a new one. */
00832     QString password = QInputDialog::getText(this,
00833                          tr("Password Authentication Required"),
00834                          tr("Please enter your control password:"),
00835                          QLineEdit::Password);
00836     if (!password.isEmpty()) {
00837       TorSettings settings;
00838       settings.setAuthenticationMethod(TorSettings::PasswordAuth);
00839       settings.setControlPassword(password);
00840       retry = true;
00841     }
00842   } else {
00843     /* Something else went wrong */
00844     int ret = VMessageBox::warning(this, 
00845                 tr("Error Authenticating to Tor"),
00846                 p(tr("Vidalia was unable to authenticate to Tor. "
00847                      "(%1)").arg(errmsg)) + 
00848                 p(tr("Please check your control port authentication "
00849                      "settings.")),
00850                 VMessageBox::ShowSettings|VMessageBox::Default,
00851                 VMessageBox::Cancel|VMessageBox::Escape);
00852     
00853     if (ret == VMessageBox::ShowSettings)
00854       showConfigDialog(ConfigDialog::Advanced);
00855   }
00856   
00857   if (_torControl->isRunning() && _isVidaliaRunningTor) 
00858     stop();
00859   else if (_torControl->isConnected())
00860     disconnect();
00861   if (retry)
00862     start();
00863 }
00864 
00865 /** Searches for and attempts to load the control authentication cookie. This
00866  * assumes the cookie is named 'control_auth_cookie'. If <b>cookiePath</b> is
00867  * empty, this method will search some default locations depending on the
00868  * current platform. <b>cookiePath</b> can point to either a cookie file or a
00869  * directory containing the cookie file. */
00870 QByteArray
00871 MainWindow::loadControlCookie(QString cookiePath)
00872 {
00873   QFile authCookie;
00874   QStringList pathList;
00875 
00876   if (!cookiePath.isEmpty()) {
00877     pathList << cookiePath;
00878   } else {
00879     /* Try some default locations */
00880     TorSettings settings;
00881     QString dataDir = settings.getDataDirectory();
00882     if (!dataDir.isEmpty())
00883       pathList << dataDir;
00884       
00885 #if defined(Q_WS_WIN)
00886     pathList << expand_filename("%APPDATA%\\Tor");
00887 #else
00888     pathList << expand_filename("~/.tor");
00889 #endif
00890   }
00891   
00892   /* Search for the cookie file */
00893   foreach (QString path, pathList) {
00894     QString cookieFile = QFileInfo(path).isFile() ?
00895                           path : path + "/control_auth_cookie";
00896     vDebug("Checking for authentication cookie in '%1'").arg(cookieFile);
00897     if (!QFileInfo(cookieFile).exists())
00898       continue;
00899     
00900     authCookie.setFileName(cookieFile);
00901     if (authCookie.open(QIODevice::ReadOnly)) {
00902       vInfo("Reading authentication cookie from '%1'").arg(cookieFile);
00903       return authCookie.readAll();
00904     } else {
00905       vWarn("Couldn't open cookie file '%1': %2")
00906         .arg(cookieFile).arg(authCookie.errorString());
00907     }
00908   }
00909   vWarn("Couldn't find a readable authentication cookie.");
00910   return QByteArray();
00911 }
00912 
00913 /** Creates and displays Vidalia's About dialog. */
00914 void
00915 MainWindow::showAboutDialog()
00916 {
00917   static AboutDialog *aboutDialog = 0;
00918   if (!aboutDialog)
00919     aboutDialog = new AboutDialog(this);
00920   aboutDialog->showWindow();
00921 }
00922 
00923 /** Creates and displays the Configuration dialog with the current page set to
00924  * <b>page</b>. */
00925 void
00926 MainWindow::showConfigDialog(ConfigDialog::Page page)
00927 {
00928   static ConfigDialog *configDialog = 0;
00929   if (!configDialog)
00930     configDialog = new ConfigDialog(this);
00931   configDialog->showWindow(page);
00932 }
00933 
00934 /** Displays the Configuration dialog, set to the Server page. */
00935 void
00936 MainWindow::showServerConfigDialog()
00937 {
00938   showConfigDialog(ConfigDialog::Server);
00939 }
00940 
00941 /** Called when the user selects the "New Identity" action from the menu. */
00942 void
00943 MainWindow::newIdentity()
00944 {
00945   QString errmsg;
00946 
00947   /* Send the NEWNYM signal. If message balloons are supported and the NEWNYM
00948    * is successful, we will show the result as a balloon. Otherwise, we'll 
00949    * just use a message box. */
00950   if (_torControl->signal(TorSignal::NewNym, &errmsg)) {
00951     /* NEWNYM signal was successful */
00952     QString title = tr("New Identity");
00953     QString message = tr("All subsequent connections will "
00954                          "appear to be different than your "
00955                          "old connections.");
00956 
00957     /* Disable the New Identity button for MIN_NEWIDENTITY_INTERVAL */
00958     _newIdentityAct->setEnabled(false);
00959     ui.lblNewIdentity->setEnabled(false);
00960     QTimer::singleShot(MIN_NEWIDENTITY_INTERVAL, 
00961                        this, SLOT(enableNewIdentity()));
00962 
00963 #if defined(USE_QSYSTEMTRAYICON)
00964     /* Check if we support balloon messages. We support balloon messages only
00965      * if we are built with Qt >= 4.2, but not if we are running on OS X or
00966      * a version of Windows <= Windows 2000. */
00967 # if defined(Q_WS_WIN)
00968     if (QSystemTrayIcon::supportsMessages() &&
00969         QSysInfo::WindowsVersion > QSysInfo::WV_2000)
00970 # else
00971     if (QSystemTrayIcon::supportsMessages())
00972 # endif
00973       _trayIcon.showMessage(title, message, QSystemTrayIcon::Information);
00974     else
00975       VMessageBox::information(this, title, message, VMessageBox::Ok);
00976 #else
00977     /* No QSystemTrayIcon. Just show a message box */
00978     VMessageBox::information(this, title, message, VMessageBox::Ok);
00979 #endif
00980   } else {
00981     /* NEWNYM signal failed */
00982     VMessageBox::warning(this, 
00983       tr("Failed to Create New Identity"), errmsg, VMessageBox::Ok);
00984   }
00985 }
00986 
00987 /** Re-enables the 'New Identity' button after a delay from the previous time
00988  * 'New Identity' was used. */
00989 void
00990 MainWindow::enableNewIdentity()
00991 {
00992   if (_torControl->isConnected()) {
00993     _newIdentityAct->setEnabled(true);
00994     ui.lblNewIdentity->setEnabled(true);
00995   }
00996 }
00997 
00998 /** Converts a TorStatus enum value to a string for debug logging purposes. */
00999 QString
01000 MainWindow::toString(TorStatus status)
01001 {
01002   switch (status) {
01003     /* These strings only appear in debug logs, so they should not be
01004      * translated. */
01005     case Unset:     return "Unset";
01006     case Stopping:  return "Stopping";
01007     case Stopped:   return "Stopped";
01008     case Starting:  return "Starting";
01009     case Started:   return "Started";
01010     case Connecting:  return "Connecting";
01011     case Connected:   return "Connected";
01012     case Disconnecting: return "Disconnecting";
01013     case Disconnected:  return "Disconnected";
01014     case Authenticating:  return "Authenticating";
01015     case Authenticated:   return "Authenticated";
01016     default: break;
01017   }
01018   return "Unknown";
01019 }
01020 

Generated on Wed Sep 5 15:49:27 2007 for Vidalia by  doxygen 1.5.3