Vidalia 0.2.10
|
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 StatusEventWidget.h 00013 ** \version $Id: StatusEventWidget.cpp 4180 2009-12-13 21:29:25Z edmanm $ 00014 ** \brief Displays information on Tor status events 00015 */ 00016 00017 #include "StatusEventWidget.h" 00018 #include "StatusEventItem.h" 00019 #include "StatusEventItemDelegate.h" 00020 #include "Vidalia.h" 00021 00022 #include "TorEvents.h" 00023 #include "stringutil.h" 00024 00025 #include <QTime> 00026 #include <QMenu> 00027 #include <QPainter> 00028 #include <QPixmap> 00029 #include <QStringList> 00030 #include <QObject> 00031 #include <QHeaderView> 00032 #include <QClipboard> 00033 00034 bool compareStatusEventItems(const QTreeWidgetItem *a, 00035 const QTreeWidgetItem *b) 00036 { 00037 return (*a < *b); 00038 } 00039 00040 StatusEventWidget::StatusEventWidget(QWidget *parent) 00041 : QTreeWidget(parent) 00042 { 00043 TorControl *tc = Vidalia::torControl(); 00044 tc->setEvent(TorEvents::GeneralStatus); 00045 tc->setEvent(TorEvents::ClientStatus); 00046 tc->setEvent(TorEvents::ServerStatus); 00047 00048 connect(this, SIGNAL(customContextMenuRequested(QPoint)), 00049 this, SLOT(customContextMenuRequested(QPoint))); 00050 connect(tc, SIGNAL(authenticated()), this, SLOT(authenticated())); 00051 connect(tc, SIGNAL(disconnected()), this, SLOT(disconnected())); 00052 connect(tc, SIGNAL(dangerousTorVersion(tc::TorVersionStatus, QString, 00053 QStringList)), 00054 this, SLOT(dangerousTorVersion(tc::TorVersionStatus, QString, 00055 QStringList))); 00056 connect(tc, SIGNAL(circuitEstablished()), this, SLOT(circuitEstablished())); 00057 connect(tc, SIGNAL(bug(QString)), this, SLOT(bug(QString))); 00058 connect(tc, SIGNAL(clockSkewed(int, QString)), 00059 this, SLOT(clockSkewed(int, QString))); 00060 connect(tc, SIGNAL(dangerousPort(quint16, bool)), 00061 this, SLOT(dangerousPort(quint16, bool))); 00062 connect(tc, SIGNAL(socksError(tc::SocksError, QString)), 00063 this, SLOT(socksError(tc::SocksError, QString))); 00064 connect(tc, SIGNAL(externalAddressChanged(QHostAddress, QString)), 00065 this, SLOT(externalAddressChanged(QHostAddress, QString))); 00066 connect(tc, SIGNAL(dnsHijacked()), this, SLOT(dnsHijacked())); 00067 connect(tc, SIGNAL(dnsUseless()), this, SLOT(dnsUseless())); 00068 connect(tc, SIGNAL(checkingOrPortReachability(QHostAddress, quint16)), 00069 this, SLOT(checkingOrPortReachability(QHostAddress, quint16))); 00070 connect(tc, SIGNAL(orPortReachabilityFinished(QHostAddress, quint16, bool)), 00071 this, SLOT(orPortReachabilityFinished(QHostAddress, quint16, bool))); 00072 connect(tc, SIGNAL(checkingDirPortReachability(QHostAddress, quint16)), 00073 this, SLOT(checkingDirPortReachability(QHostAddress, quint16))); 00074 connect(tc, SIGNAL(dirPortReachabilityFinished(QHostAddress, quint16, bool)), 00075 this, SLOT(dirPortReachabilityFinished(QHostAddress, quint16, bool))); 00076 connect(tc, SIGNAL(serverDescriptorRejected(QHostAddress, quint16, QString)), 00077 this, SLOT(serverDescriptorRejected(QHostAddress, quint16, QString))); 00078 connect(tc, SIGNAL(serverDescriptorAccepted(QHostAddress, quint16)), 00079 this, SLOT(serverDescriptorAccepted(QHostAddress, quint16))); 00080 00081 setItemDelegate(new StatusEventItemDelegate(this)); 00082 } 00083 00084 void 00085 StatusEventWidget::retranslateUi() 00086 { 00087 /* XXX: We need to store the untranslated text for each of the status 00088 * event items, iterate through all items in the list, and then 00089 * retranslate them. The trick is that some of the messages are 00090 * generated dynamically based on data sent by Tor (which is NOT 00091 * translated). Those messages we can't retranslate correctly 00092 * without also storing the variables used to generate the message. 00093 */ 00094 } 00095 00096 void 00097 StatusEventWidget::setMaximumItemCount(int maximumItemCount) 00098 { 00099 _maximumItemCount = maximumItemCount; 00100 00101 QTreeWidgetItem *item; 00102 Qt::SortOrder order = header()->sortIndicatorOrder(); 00103 while (topLevelItemCount() > _maximumItemCount) { 00104 if (order == Qt::AscendingOrder) 00105 item = takeTopLevelItem(0); 00106 else 00107 item = takeTopLevelItem(topLevelItemCount()-1); 00108 if (item) 00109 delete item; 00110 } 00111 } 00112 00113 int 00114 StatusEventWidget::maximumItemCount() const 00115 { 00116 return _maximumItemCount; 00117 } 00118 00119 QStringList 00120 StatusEventWidget::selectedEvents() const 00121 { 00122 QString text; 00123 QStringList out; 00124 QList<QTreeWidgetItem *> items = selectedItems(); 00125 00126 // We have to sort the items since selectedItems() returns the order in 00127 // which the items were selected, not the order in which they appear in the 00128 // current list. 00129 qStableSort(items.begin(), items.end(), compareStatusEventItems); 00130 00131 for (int i = 0; i < items.size(); i++) { 00132 StatusEventItem *event = dynamic_cast<StatusEventItem *>(items.at(i)); 00133 if (event) 00134 out.append(event->toString()); 00135 } 00136 return out; 00137 } 00138 00139 QStringList 00140 StatusEventWidget::allEvents() const 00141 { 00142 QStringList out; 00143 QList<QTreeWidgetItem *> items; 00144 00145 for (int i = 0; i < topLevelItemCount(); i++) 00146 items.append(topLevelItem(i)); 00147 00148 // Ensure the items are sorted in ascending order according to timestamp 00149 qStableSort(items.begin(), items.end(), compareStatusEventItems); 00150 00151 for (int i = 0; i < items.size(); i++) { 00152 StatusEventItem *event = dynamic_cast<StatusEventItem *>(items.at(i)); 00153 if (event) 00154 out.append(event->toString()); 00155 } 00156 return out; 00157 } 00158 00159 void 00160 StatusEventWidget::customContextMenuRequested(const QPoint &pos) 00161 { 00162 QMenu menu(this); 00163 00164 StatusEventItem *item = dynamic_cast<StatusEventItem *>(itemAt(pos)); 00165 if (! item || ! item->isSelected()) 00166 return; 00167 00168 QAction *copyAction = menu.addAction(QIcon(":/images/22x22/edit-copy.png"), 00169 tr("Copy to Clipboard")); 00170 00171 QAction *action = menu.exec(mapToGlobal(pos)); 00172 if (action == copyAction) { 00173 QStringList eventText = selectedEvents(); 00174 if (! eventText.isEmpty()) 00175 QApplication::clipboard()->setText(eventText.join("\n")); 00176 } 00177 } 00178 00179 QList<StatusEventItem *> 00180 StatusEventWidget::find(const QString &text, bool highlight) 00181 { 00182 QList<StatusEventItem *> items; 00183 00184 for (int i = 0; i < topLevelItemCount(); i++) { 00185 StatusEventItem *item = dynamic_cast<StatusEventItem *>(topLevelItem(i)); 00186 if (! item) 00187 continue; 00188 00189 if (item->title().contains(text, Qt::CaseInsensitive) 00190 || item->description().contains(text, Qt::CaseInsensitive)) { 00191 items.append(item); 00192 if (highlight) 00193 item->setSelected(true); 00194 } else if (highlight) { 00195 item->setSelected(false); 00196 } 00197 } 00198 return items; 00199 } 00200 00201 void 00202 StatusEventWidget::addNotification(const QPixmap &icon, 00203 const QString &title, 00204 const QString &description, 00205 const QString &helpUrl) 00206 { 00207 // Check if we first need to remove the oldest item in the list in order 00208 // to avoid exceeding the maximum number of notification items 00209 if (topLevelItemCount() == maximumItemCount()) { 00210 QTreeWidgetItem *item; 00211 if (header()->sortIndicatorOrder() == Qt::AscendingOrder) 00212 item = takeTopLevelItem(0); 00213 else 00214 item = takeTopLevelItem(topLevelItemCount()-1); 00215 if (item) 00216 delete item; 00217 } 00218 00219 // Create the new notification item 00220 StatusEventItem *item = new StatusEventItem(this); 00221 item->setTimestamp(QTime::currentTime()); 00222 item->setIcon(icon); 00223 item->setTitle(title); 00224 item->setDescription(description); 00225 item->setHelpUrl(helpUrl); 00226 item->setToolTip(string_wrap(description, 80)); 00227 00228 // Add the new item to the list and ensure it is visible 00229 addTopLevelItem(item); 00230 scrollToItem(item, QAbstractItemView::EnsureVisible); 00231 } 00232 00233 QPixmap 00234 StatusEventWidget::addBadgeToPixmap(const QPixmap &pixmap, 00235 const QPixmap &badge) 00236 { 00237 QPixmap out = pixmap; 00238 QPainter painter(&out); 00239 painter.drawPixmap(pixmap.width() - badge.width(), 00240 pixmap.height() - badge.height(), 00241 badge); 00242 return out; 00243 } 00244 00245 QPixmap 00246 StatusEventWidget::addBadgeToPixmap(const QPixmap &pixmap, 00247 const QString &badge) 00248 { 00249 return StatusEventWidget::addBadgeToPixmap(pixmap, QPixmap(badge)); 00250 } 00251 00252 QPixmap 00253 StatusEventWidget::addBadgeToPixmap(const QString &pixmap, 00254 const QString &badge) 00255 { 00256 return StatusEventWidget::addBadgeToPixmap(QPixmap(pixmap), QPixmap(badge)); 00257 } 00258 00259 void 00260 StatusEventWidget::authenticated() 00261 { 00262 TorControl *tc = Vidalia::torControl(); 00263 00264 QString version = tc->getTorVersionString(); 00265 QPixmap icon = addBadgeToPixmap(":/images/48x48/tor-logo.png", 00266 ":/images/32x32/dialog-ok-apply.png"); 00267 addNotification(icon, 00268 tr("The Tor Software is Running"), 00269 tr("You are currently running version \"%1\" of the Tor software.") 00270 .arg(version)); 00271 00272 // Check if Tor established a circuit before we were able to authenticate, 00273 // in which case we missed the CIRCUIT_ESTABLISHED event. So fake it. 00274 if (tc->isCircuitEstablished()) 00275 circuitEstablished(); 00276 00277 // Check on the status of Tor's version, in case we missed that event too 00278 QString status = tc->getInfo("status/version/current").toString(); 00279 if (! status.compare("old", Qt::CaseInsensitive) 00280 || ! status.compare("obsolete", Qt::CaseInsensitive)) { 00281 dangerousTorVersion(tc::ObsoleteTorVersion, version, QStringList()); 00282 } else if (! status.compare("unrecommended", Qt::CaseInsensitive)) { 00283 dangerousTorVersion(tc::UnrecommendedTorVersion, version, QStringList()); 00284 } 00285 } 00286 00287 void 00288 StatusEventWidget::disconnected() 00289 { 00290 QPixmap icon = addBadgeToPixmap(":/images/48x48/tor-logo.png", 00291 ":/images/32x32/edit-delete.png"); 00292 00293 addNotification(icon, 00294 tr("The Tor Software is not Running"), 00295 tr("Click \"Start Tor\" in the Vidalia Control Panel to restart the Tor " 00296 "software. If Tor exited unexpectedly, select the \"Advanced\" tab " 00297 "above for details about any errors encountered.")); 00298 00299 _squelchDescriptorAcceptedEvent = false; 00300 } 00301 00302 void 00303 StatusEventWidget::dangerousTorVersion(tc::TorVersionStatus reason, 00304 const QString &version, 00305 const QStringList &recommended) 00306 { 00307 Q_UNUSED(recommended); 00308 QString description; 00309 QPixmap icon; 00310 00311 if (reason == tc::UnrecommendedTorVersion) { 00312 icon = addBadgeToPixmap(":/images/48x48/tor-logo.png", 00313 ":/images/32x32/security-medium.png"); 00314 00315 description = 00316 tr("You are currently running version \"%1\" of the Tor software, which " 00317 "is no longer recommended. Please upgrade to the most recent version " 00318 "of the software, which may contain important security, reliability " 00319 "and performance fixes.").arg(version); 00320 } else if (reason == tc::ObsoleteTorVersion) { 00321 icon = addBadgeToPixmap(":/images/48x48/tor-logo.png", 00322 ":/images/32x32/security-low.png"); 00323 00324 description = 00325 tr("You are currently running version \"%1\" of the Tor software, which " 00326 "may no longer work with the current Tor network. Please upgrade " 00327 "to the most recent version of the software, which may contain " 00328 "important security, reliability and performance fixes.").arg(version); 00329 } 00330 00331 addNotification(icon, tr("Your Tor Software is Out-of-date"), description); 00332 } 00333 00334 void 00335 StatusEventWidget::circuitEstablished() 00336 { 00337 addNotification(QPixmap(":/images/48x48/network-connect.png"), 00338 tr("Connected to the Tor Network"), 00339 tr("We were able to successfully establish a connection to the Tor " 00340 "network. You can now configure your applications to use the Internet " 00341 "anonymously.")); 00342 } 00343 00344 void 00345 StatusEventWidget::bug(const QString &description) 00346 { 00347 QPixmap icon = addBadgeToPixmap(":/images/48x48/tor-logo.png", 00348 ":/images/32x32/script-error.png"); 00349 addNotification(icon, 00350 tr("Tor Software Error"), 00351 tr("The Tor software encountered an internal bug. Please report the " 00352 "following error message to the Tor developers at bugs.torproject.org: " 00353 "\"%1\"").arg(description)); 00354 } 00355 00356 void 00357 StatusEventWidget::clockSkewed(int skew, const QString &source) 00358 { 00359 if (source.startsWith("OR:", Qt::CaseInsensitive)) { 00360 // Tor versions 0.2.1.19 and earlier, and 0.2.2.1 and earlier, throw 00361 // this message a little too liberally in this case. 00362 quint32 torVersion = Vidalia::torControl()->getTorVersion(); 00363 if (torVersion <= 0x00020113) 00364 return; 00365 QString str = Vidalia::torControl()->getTorVersionString(); 00366 if (str.startsWith("0.2.2.") && torVersion <= 0x00020201) 00367 return; 00368 } 00369 00370 QString description; 00371 QPixmap icon = addBadgeToPixmap(":/images/48x48/chronometer.png", 00372 ":/images/32x32/dialog-warning.png"); 00373 00374 if (skew < 0) { 00375 description = 00376 tr("Tor has determined that your computer's clock may be set to %1 " 00377 "seconds in the past compared to the source \"%2\". If your " 00378 "clock is not correct, Tor will not be able to function. Please " 00379 "verify your computer displays the correct time.").arg(qAbs(skew)) 00380 .arg(source); 00381 } else { 00382 description = 00383 tr("Tor has determined that your computer's clock may be set to %1 " 00384 "seconds in the future compared to the source \"%2\". If " 00385 "your clock is not correct, Tor will not be able to function. Please " 00386 "verify your computer displays the correct time.").arg(qAbs(skew)) 00387 .arg(source); 00388 } 00389 addNotification(icon, tr("Your Computer's Clock is Potentially Incorrect"), 00390 description); 00391 } 00392 00393 void 00394 StatusEventWidget::dangerousPort(quint16 port, bool rejected) 00395 { 00396 QPixmap icon; 00397 QString description; 00398 00399 if (rejected) { 00400 icon = addBadgeToPixmap(":/images/48x48/applications-internet.png", 00401 ":/images/32x32/security-low.png"); 00402 00403 description = 00404 tr("One of the applications on your computer may have attempted to " 00405 "make an unencrypted connection through Tor to port %1. Sending " 00406 "unencrypted information over the Tor network is dangerous and not " 00407 "recommended. For your protection, Tor has automatically closed this " 00408 "connection.").arg(port); 00409 } else { 00410 icon = addBadgeToPixmap(":/images/48x48/applications-internet.png", 00411 ":/images/32x32/security-medium.png"); 00412 description = 00413 tr("One of the applications on your computer may have attempted to " 00414 "make an unencrypted connection through Tor to port %1. Sending " 00415 "unencrypted information over the Tor network is dangerous and not " 00416 "recommended.").arg(port); 00417 } 00418 00419 addNotification(icon, tr("Potentially Dangerous Connection!"), description); 00420 } 00421 00422 void 00423 StatusEventWidget::socksError(tc::SocksError type, const QString &destination) 00424 { 00425 QString title, description; 00426 QPixmap icon = QPixmap(":/images/48x48/applications-internet.png"); 00427 00428 if (type == tc::DangerousSocksTypeError) { 00429 icon = addBadgeToPixmap(icon, ":/images/32x32/security-medium.png"); 00430 00431 title = tr("Potentially Dangerous Connection!"); 00432 description = 00433 tr("One of your applications established a connection through Tor " 00434 "to \"%1\" using a protocol that may leak information about your " 00435 "destination. Please ensure you configure your applications to use " 00436 "only SOCKS4a or SOCKS5 with remote hostname resolution.") 00437 .arg(destination); 00438 } else if (type == tc::UnknownSocksProtocolError) { 00439 icon = addBadgeToPixmap(icon, ":/images/32x32/dialog-warning.png"); 00440 00441 title = tr("Unknown SOCKS Protocol"); 00442 description = 00443 tr("One of your applications tried to establish a connection through " 00444 "Tor using a protocol that Tor does not understand. Please ensure " 00445 "you configure your applications to use only SOCKS4a or SOCKS5 with " 00446 "remote hostname resolution."); 00447 } else if (type == tc::BadSocksHostnameError) { 00448 icon = addBadgeToPixmap(icon, ":/images/32x32/dialog-warning.png"); 00449 00450 title = tr("Invalid Destination Hostname"); 00451 description = 00452 tr("One of your applications tried to establish a connection through " 00453 "Tor to \"%1\", which Tor does not recognize as a valid hostname. " 00454 "Please check your application's configuration.").arg(destination); 00455 } else { 00456 return; 00457 } 00458 00459 addNotification(icon, title, description); 00460 } 00461 00462 void 00463 StatusEventWidget::externalAddressChanged(const QHostAddress &ip, 00464 const QString &hostname) 00465 { 00466 QString hostString = hostname.isEmpty() ? QString() 00467 : QString(" (%1)").arg(hostname); 00468 00469 addNotification(QPixmap(":/images/48x48/applications-internet.png"), 00470 tr("External IP Address Changed"), 00471 tr("Tor has determined your relay's public IP address is currently %1%2. " 00472 "If that is not correct, please consider setting the 'Address' option " 00473 "in your relay's configuration.").arg(ip.toString()).arg(hostString)); 00474 } 00475 00476 void 00477 StatusEventWidget::dnsHijacked() 00478 { 00479 QPixmap icon = addBadgeToPixmap(":/images/48x48/applications-internet.png", 00480 ":/images/32x32/dialog-warning.png"); 00481 addNotification(icon, 00482 tr("DNS Hijacking Detected"), 00483 tr("Tor detected that your DNS provider is providing false responses for " 00484 "domains that do not exist. Some ISPs and other DNS providers, such as " 00485 "OpenDNS, are known to do this in order to display their own search or " 00486 "advertising pages.")); 00487 } 00488 00489 void 00490 StatusEventWidget::dnsUseless() 00491 { 00492 QPixmap icon = addBadgeToPixmap(":/images/48x48/applications-internet.png", 00493 ":/images/32x32/edit-delete.png"); 00494 addNotification(icon, 00495 tr("DNS Hijacking Detected"), 00496 tr("Tor detected that your DNS provider is providing false responses for " 00497 "well known domains. Since clients rely on Tor network relays to " 00498 "provide accurate DNS repsonses, your relay will not be configured as " 00499 "an exit relay.")); 00500 } 00501 00502 void 00503 StatusEventWidget::checkingOrPortReachability(const QHostAddress &ip, 00504 quint16 port) 00505 { 00506 addNotification(QPixmap(":/images/48x48/network-wired.png"), 00507 tr("Checking Server Port Reachability"), 00508 tr("Tor is trying to determine if your relay's server port is reachable " 00509 "from the Tor network by connecting to itself at %1:%2. This test " 00510 "could take several minutes.").arg(ip.toString()).arg(port)); 00511 } 00512 00513 void 00514 StatusEventWidget::orPortReachabilityFinished(const QHostAddress &ip, 00515 quint16 port, 00516 bool reachable) 00517 { 00518 QString title, description; 00519 QPixmap icon = QPixmap(":/images/48x48/network-wired.png"); 00520 if (reachable) { 00521 icon = addBadgeToPixmap(icon, ":/images/32x32/dialog-ok-apply.png"); 00522 title = tr("Server Port Reachability Test Successful!"); 00523 description = 00524 tr("Your relay's server port is reachable from the Tor network!"); 00525 } else { 00526 icon = addBadgeToPixmap(icon, ":/images/32x32/dialog-warning.png"); 00527 title = tr("Server Port Reachability Test Failed"); 00528 description = 00529 tr("Your relay's server port is not reachable by other Tor clients. This " 00530 "can happen if you are behind a router or firewall that requires you " 00531 "to set up port forwarding. If %1:%2 is not your correct IP address " 00532 "and server port, please check your relay's configuration.") 00533 .arg(ip.toString()).arg(port); 00534 } 00535 00536 addNotification(icon, title, description); 00537 } 00538 00539 void 00540 StatusEventWidget::checkingDirPortReachability(const QHostAddress &ip, 00541 quint16 port) 00542 { 00543 addNotification(QPixmap(":/images/48x48/network-wired.png"), 00544 tr("Checking Directory Port Reachability"), 00545 tr("Tor is trying to determine if your relay's directory port is reachable " 00546 "from the Tor network by connecting to itself at %1:%2. This test " 00547 "could take several minutes.").arg(ip.toString()).arg(port)); 00548 } 00549 00550 void 00551 StatusEventWidget::dirPortReachabilityFinished(const QHostAddress &ip, 00552 quint16 port, 00553 bool reachable) 00554 { 00555 QString title, description; 00556 QPixmap icon = QPixmap(":/images/48x48/network-wired.png"); 00557 if (reachable) { 00558 icon = addBadgeToPixmap(icon, ":/images/32x32/dialog-ok-apply.png"); 00559 title = tr("Directory Port Reachability Test Successful!"); 00560 description = 00561 tr("Your relay's directory port is reachable from the Tor network!"); 00562 } else { 00563 icon = addBadgeToPixmap(icon, ":/images/32x32/dialog-warning.png"); 00564 title = tr("Directory Port Reachability Test Failed"); 00565 description = 00566 tr("Your relay's directory port is not reachable by other Tor clients. " 00567 "This can happen if you are behind a router or firewall that requires " 00568 "you to set up port forwarding. If %1:%2 is not your correct IP " 00569 "address and directory port, please check your relay's configuration.") 00570 .arg(ip.toString()).arg(port); 00571 } 00572 00573 addNotification(icon, title, description); 00574 } 00575 00576 void 00577 StatusEventWidget::serverDescriptorRejected(const QHostAddress &ip, 00578 quint16 port, 00579 const QString &reason) 00580 { 00581 QPixmap icon = 00582 addBadgeToPixmap(":/images/48x48/preferences-system-network-sharing.png", 00583 ":/images/32x32/dialog-warning.png"); 00584 00585 addNotification(icon, 00586 tr("Relay Descriptor Rejected"), 00587 tr("Your relay's descriptor, which enables clients to connect to your " 00588 "relay, was rejected by the directory server at %1:%2. The reason " 00589 "given was: %3").arg(ip.toString()).arg(port).arg(reason)); 00590 } 00591 00592 void 00593 StatusEventWidget::serverDescriptorAccepted(const QHostAddress &ip, 00594 quint16 port) 00595 { 00596 Q_UNUSED(ip); 00597 Q_UNUSED(port); 00598 00599 if (_squelchDescriptorAcceptedEvent) 00600 return; 00601 _squelchDescriptorAcceptedEvent = true; 00602 00603 QPixmap icon = 00604 addBadgeToPixmap(":/images/48x48/preferences-system-network-sharing.png", 00605 ":/images/32x32/dialog-ok-apply.png"); 00606 00607 addNotification(icon, 00608 tr("Your Relay is Online"), 00609 tr("Your relay is now online and available for Tor clients to use. You " 00610 "should see an increase in network traffic shown by the Bandwidth " 00611 "Graph within a few hours as more clients learn about your relay. " 00612 "Thank you for contributing to the Tor network!")); 00613 } 00614