summaryrefslogtreecommitdiffstats
path: root/ui/qt/interface_frame.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:34:10 +0000
commite4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch)
tree68cb5ef9081156392f1dd62a00c6ccc1451b93df /ui/qt/interface_frame.cpp
parentInitial commit. (diff)
downloadwireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz
wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ui/qt/interface_frame.cpp')
-rw-r--r--ui/qt/interface_frame.cpp542
1 files changed, 542 insertions, 0 deletions
diff --git a/ui/qt/interface_frame.cpp b/ui/qt/interface_frame.cpp
new file mode 100644
index 0000000..b1f34b2
--- /dev/null
+++ b/ui/qt/interface_frame.cpp
@@ -0,0 +1,542 @@
+/* interface_frame.cpp
+ * Display of interfaces, including their respective data, and the
+ * capability to filter interfaces by type
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "config.h"
+#include <ui_interface_frame.h>
+
+#include "capture/capture_ifinfo.h"
+
+#ifdef Q_OS_WIN
+#include "capture/capture-wpcap.h"
+#endif
+
+#include "ui/qt/interface_frame.h"
+#include <ui/qt/simple_dialog.h>
+#include <ui/qt/main_application.h>
+
+#include <ui/qt/models/interface_tree_model.h>
+#include <ui/qt/models/sparkline_delegate.h>
+
+#include <ui/qt/utils/color_utils.h>
+
+
+#include "extcap.h"
+
+#include <ui/recent.h>
+#include "capture_opts.h"
+#include "ui/capture_globals.h"
+#include <wsutil/utf8_entities.h>
+
+#include <QDesktopServices>
+#include <QFrame>
+#include <QHBoxLayout>
+#include <QItemSelection>
+#include <QLabel>
+#include <QPushButton>
+#include <QUrl>
+
+#include <epan/prefs.h>
+
+#define BTN_IFTYPE_PROPERTY "ifType"
+
+#ifdef HAVE_LIBPCAP
+const int stat_update_interval_ = 1000; // ms
+#endif
+const char *no_capture_link = "#no_capture";
+
+InterfaceFrame::InterfaceFrame(QWidget * parent)
+: QFrame(parent),
+ ui(new Ui::InterfaceFrame)
+ , proxy_model_(Q_NULLPTR)
+ , source_model_(Q_NULLPTR)
+ , info_model_(this)
+#ifdef HAVE_LIBPCAP
+ ,stat_timer_(NULL)
+#endif // HAVE_LIBPCAP
+{
+ ui->setupUi(this);
+
+ setStyleSheet(QString(
+ "QFrame {"
+ " border: 0;"
+ "}"
+ "QTreeView {"
+ " border: 0;"
+ "}"
+ ));
+
+ ui->warningLabel->hide();
+
+#ifdef Q_OS_MAC
+ ui->interfaceTree->setAttribute(Qt::WA_MacShowFocusRect, false);
+#endif
+
+ /* TODO: There must be a better way to do this */
+ ifTypeDescription.insert(IF_WIRED, tr("Wired"));
+ ifTypeDescription.insert(IF_AIRPCAP, tr("AirPCAP"));
+ ifTypeDescription.insert(IF_PIPE, tr("Pipe"));
+ ifTypeDescription.insert(IF_STDIN, tr("STDIN"));
+ ifTypeDescription.insert(IF_BLUETOOTH, tr("Bluetooth"));
+ ifTypeDescription.insert(IF_WIRELESS, tr("Wireless"));
+ ifTypeDescription.insert(IF_DIALUP, tr("Dial-Up"));
+ ifTypeDescription.insert(IF_USB, tr("USB"));
+ ifTypeDescription.insert(IF_EXTCAP, tr("External Capture"));
+ ifTypeDescription.insert(IF_VIRTUAL, tr ("Virtual"));
+
+ QList<InterfaceTreeColumns> columns;
+ columns.append(IFTREE_COL_EXTCAP);
+ columns.append(IFTREE_COL_DISPLAY_NAME);
+ columns.append(IFTREE_COL_STATS);
+ proxy_model_.setColumns(columns);
+ proxy_model_.setStoreOnChange(true);
+ proxy_model_.setSortByActivity(true);
+ proxy_model_.setSourceModel(&source_model_);
+
+ info_model_.setSourceModel(&proxy_model_);
+ info_model_.setColumn(static_cast<int>(columns.indexOf(IFTREE_COL_STATS)));
+
+ ui->interfaceTree->setModel(&info_model_);
+ ui->interfaceTree->setSortingEnabled(true);
+
+ ui->interfaceTree->setItemDelegateForColumn(proxy_model_.mapSourceToColumn(IFTREE_COL_STATS), new SparkLineDelegate(this));
+
+ ui->interfaceTree->setContextMenuPolicy(Qt::CustomContextMenu);
+ connect(ui->interfaceTree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
+
+ connect(mainApp, SIGNAL(appInitialized()), this, SLOT(interfaceListChanged()));
+ connect(mainApp, SIGNAL(localInterfaceListChanged()), this, SLOT(interfaceListChanged()));
+
+ connect(ui->interfaceTree->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
+ this, SLOT(interfaceTreeSelectionChanged(const QItemSelection &, const QItemSelection &)));
+}
+
+InterfaceFrame::~InterfaceFrame()
+{
+ delete ui;
+}
+
+QMenu * InterfaceFrame::getSelectionMenu()
+{
+ QMenu * contextMenu = new QMenu(this);
+ QList<int> typesDisplayed = proxy_model_.typesDisplayed();
+
+ QMap<int, QString>::const_iterator it = ifTypeDescription.constBegin();
+ while (it != ifTypeDescription.constEnd())
+ {
+ int ifType = it.key();
+
+ if (typesDisplayed.contains(ifType))
+ {
+ QAction *endp_action = new QAction(it.value(), this);
+ endp_action->setData(QVariant::fromValue(ifType));
+ endp_action->setCheckable(true);
+ endp_action->setChecked(proxy_model_.isInterfaceTypeShown(ifType));
+ connect(endp_action, SIGNAL(triggered()), this, SLOT(triggeredIfTypeButton()));
+ contextMenu->addAction(endp_action);
+ }
+ ++it;
+ }
+
+#ifdef HAVE_PCAP_REMOTE
+ if (proxy_model_.remoteInterfacesExist())
+ {
+ QAction * toggleRemoteAction = new QAction(tr("Remote interfaces"), this);
+ toggleRemoteAction->setCheckable(true);
+ toggleRemoteAction->setChecked(proxy_model_.remoteDisplay());
+ connect(toggleRemoteAction, SIGNAL(triggered()), this, SLOT(toggleRemoteInterfaces()));
+ contextMenu->addAction(toggleRemoteAction);
+ }
+#endif
+
+ contextMenu->addSeparator();
+ QAction * toggleHideAction = new QAction(tr("Show hidden interfaces"), this);
+ toggleHideAction->setCheckable(true);
+ toggleHideAction->setChecked(! proxy_model_.filterHidden());
+ connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHiddenInterfaces()));
+ contextMenu->addAction(toggleHideAction);
+
+ return contextMenu;
+}
+
+int InterfaceFrame::interfacesHidden()
+{
+ return proxy_model_.interfacesHidden();
+}
+
+int InterfaceFrame::interfacesPresent()
+{
+ return source_model_.rowCount() - proxy_model_.interfacesHidden();
+}
+
+void InterfaceFrame::ensureSelectedInterface()
+{
+#ifdef HAVE_LIBPCAP
+ if (interfacesPresent() < 1) return;
+
+ if (source_model_.selectedDevices().count() < 1) {
+ QModelIndex first_idx = info_model_.mapFromSource(proxy_model_.index(0, 0));
+ ui->interfaceTree->setCurrentIndex(first_idx);
+ }
+
+ ui->interfaceTree->setFocus();
+#endif
+}
+
+void InterfaceFrame::hideEvent(QHideEvent *) {
+#ifdef HAVE_LIBPCAP
+ if (stat_timer_)
+ stat_timer_->stop();
+ source_model_.stopStatistic();
+#endif // HAVE_LIBPCAP
+}
+
+void InterfaceFrame::showEvent(QShowEvent *) {
+
+#ifdef HAVE_LIBPCAP
+ if (stat_timer_)
+ stat_timer_->start(stat_update_interval_);
+#endif // HAVE_LIBPCAP
+}
+
+void InterfaceFrame::actionButton_toggled(bool checked)
+{
+ QVariant ifType = sender()->property(BTN_IFTYPE_PROPERTY);
+ if (ifType.isValid())
+ {
+ proxy_model_.setInterfaceTypeVisible(ifType.toInt(), checked);
+ }
+
+ resetInterfaceTreeDisplay();
+}
+
+void InterfaceFrame::triggeredIfTypeButton()
+{
+ QAction *sender = qobject_cast<QAction *>(QObject::sender());
+ if (sender)
+ {
+ int ifType = sender->data().value<int>();
+ proxy_model_.toggleTypeVisibility(ifType);
+
+ resetInterfaceTreeDisplay();
+ emit typeSelectionChanged();
+ }
+}
+
+void InterfaceFrame::interfaceListChanged()
+{
+ info_model_.clearInfos();
+ if (prefs.capture_no_extcap)
+ info_model_.appendInfo(tr("External capture interfaces disabled."));
+
+ resetInterfaceTreeDisplay();
+ // Ensure that device selection is consistent with the displayed selection.
+ updateSelectedInterfaces();
+
+#ifdef HAVE_LIBPCAP
+ if (!stat_timer_) {
+ updateStatistics();
+ stat_timer_ = new QTimer(this);
+ connect(stat_timer_, SIGNAL(timeout()), this, SLOT(updateStatistics()));
+ stat_timer_->start(stat_update_interval_);
+ }
+#endif
+}
+
+void InterfaceFrame::toggleHiddenInterfaces()
+{
+ source_model_.interfaceListChanged();
+ proxy_model_.toggleFilterHidden();
+
+ emit typeSelectionChanged();
+}
+
+#ifdef HAVE_PCAP_REMOTE
+void InterfaceFrame::toggleRemoteInterfaces()
+{
+ proxy_model_.toggleRemoteDisplay();
+ emit typeSelectionChanged();
+}
+#endif
+
+void InterfaceFrame::resetInterfaceTreeDisplay()
+{
+ ui->warningLabel->hide();
+ ui->warningLabel->clear();
+
+ ui->warningLabel->setStyleSheet(QString(
+ "QLabel {"
+ " border-radius: 0.5em;"
+ " padding: 0.33em;"
+ " margin-bottom: 0.25em;"
+ // We might want to transition this to normal colors this after a timeout.
+ " background-color: %2;"
+ "}"
+ ).arg(ColorUtils::warningBackground().name()));
+
+#ifdef HAVE_LIBPCAP
+#ifdef Q_OS_WIN
+ if (!has_wpcap) {
+ ui->warningLabel->setText(tr(
+ "<p>"
+ "Local interfaces are unavailable because no packet capture driver is installed."
+ "</p><p>"
+ "You can fix this by installing <a href=\"https://npcap.com/\">Npcap</a>."
+ "</p>"));
+ } else if (!npf_sys_is_running()) {
+ ui->warningLabel->setText(tr(
+ "<p>"
+ "Local interfaces are unavailable because the packet capture driver isn't loaded."
+ "</p><p>"
+ "You can fix this by running <pre>net start npcap</pre> if you have Npcap installed"
+ " or <pre>net start npf</pre> if you have WinPcap installed."
+ " Both commands must be run as Administrator."
+ "</p>"));
+ }
+#endif
+
+ if (!haveLocalCapturePermissions())
+ {
+#ifdef Q_OS_MAC
+ //
+ // NOTE: if you change this text, you must also change the
+ // definition of PLATFORM_PERMISSIONS_SUGGESTION that is
+ // used if __APPLE__ is defined, so that it reflects the
+ // new message text.
+ //
+ QString install_chmodbpf_path = mainApp->applicationDirPath() + "/../Resources/Extras/Install ChmodBPF.pkg";
+ ui->warningLabel->setText(tr(
+ "<p>"
+ "You don't have permission to capture on local interfaces."
+ "</p><p>"
+ "You can fix this by <a href=\"file://%1\">installing ChmodBPF</a>."
+ "</p>")
+ .arg(install_chmodbpf_path));
+#else
+ //
+ // XXX - should this give similar platform-dependent recommendations,
+ // just as dumpcap gives platform-dependent recommendations in its
+ // PLATFORM_PERMISSIONS_SUGGESTION #define?
+ //
+ ui->warningLabel->setText(tr("You don't have permission to capture on local interfaces."));
+#endif
+ }
+
+ if (proxy_model_.rowCount() == 0)
+ {
+ ui->warningLabel->setText(tr("No interfaces found."));
+ ui->warningLabel->setText(proxy_model_.interfaceError());
+ if (prefs.capture_no_interface_load) {
+ ui->warningLabel->setText(tr("Interfaces not loaded (due to preference). Go to Capture " UTF8_RIGHTWARDS_ARROW " Refresh Interfaces to load."));
+ }
+ }
+
+ // XXX Should we have a separate recent pref for each message?
+ if (!ui->warningLabel->text().isEmpty() && recent.sys_warn_if_no_capture)
+ {
+ QString warning_text = ui->warningLabel->text();
+ warning_text.append(QString("<p><a href=\"%1\">%2</a></p>")
+ .arg(no_capture_link)
+ .arg(SimpleDialog::dontShowThisAgain()));
+ ui->warningLabel->setText(warning_text);
+
+ ui->warningLabel->show();
+ }
+#endif // HAVE_LIBPCAP
+
+ if (proxy_model_.rowCount() > 0)
+ {
+ ui->interfaceTree->show();
+ ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_EXTCAP));
+ ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_DISPLAY_NAME));
+ ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_STATS));
+ }
+ else
+ {
+ ui->interfaceTree->hide();
+ }
+}
+
+// XXX Should this be in capture/capture-pcap-util.[ch]?
+bool InterfaceFrame::haveLocalCapturePermissions() const
+{
+#ifdef Q_OS_MAC
+ QFileInfo bpf0_fi = QFileInfo("/dev/bpf0");
+ return bpf0_fi.isReadable() && bpf0_fi.isWritable();
+#else
+ // XXX Add checks for other platforms.
+ return true;
+#endif
+}
+
+void InterfaceFrame::updateSelectedInterfaces()
+{
+ if (source_model_.rowCount() == 0)
+ return;
+#ifdef HAVE_LIBPCAP
+ QItemSelection sourceSelection = source_model_.selectedDevices();
+ QItemSelection mySelection = info_model_.mapSelectionFromSource(proxy_model_.mapSelectionFromSource(sourceSelection));
+
+ ui->interfaceTree->selectionModel()->clearSelection();
+ ui->interfaceTree->selectionModel()->select(mySelection, QItemSelectionModel::SelectCurrent);
+#endif
+}
+
+void InterfaceFrame::interfaceTreeSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
+{
+ if (selected.count() == 0 && deselected.count() == 0)
+ return;
+ if (source_model_.rowCount() == 0)
+ return;
+
+#ifdef HAVE_LIBPCAP
+ /* Take all selected interfaces, not just the newly ones */
+ QItemSelection allSelected = ui->interfaceTree->selectionModel()->selection();
+ QItemSelection sourceSelection = proxy_model_.mapSelectionToSource(info_model_.mapSelectionToSource(allSelected));
+
+ if (source_model_.updateSelectedDevices(sourceSelection))
+ emit itemSelectionChanged();
+#endif
+}
+
+void InterfaceFrame::on_interfaceTree_doubleClicked(const QModelIndex &index)
+{
+ QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(index));
+
+ if (! realIndex.isValid())
+ return;
+
+ QStringList interfaces;
+
+#ifdef HAVE_LIBPCAP
+
+ QString device_name = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_NAME).toString();
+ QString extcap_string = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_EXTCAP_PATH).toString();
+
+ interfaces << device_name;
+
+ /* We trust the string here. If this interface is really extcap, the string is
+ * being checked immediatly before the dialog is being generated */
+ if (extcap_string.length() > 0)
+ {
+ /* this checks if configuration is required and not yet provided or saved via prefs */
+ if (extcap_requires_configuration((const char *)(device_name.toStdString().c_str())))
+ {
+ emit showExtcapOptions(device_name, true);
+ return;
+ }
+ }
+#endif
+
+ // Start capture for all columns except the first one with extcap
+ if (IFTREE_COL_EXTCAP != realIndex.column()) {
+ startCapture(interfaces);
+ }
+}
+
+#ifdef HAVE_LIBPCAP
+void InterfaceFrame::on_interfaceTree_clicked(const QModelIndex &index)
+{
+ if (index.column() == 0)
+ {
+ QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(index));
+
+ if (! realIndex.isValid())
+ return;
+
+ QString device_name = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_NAME).toString();
+ QString extcap_string = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_EXTCAP_PATH).toString();
+
+ /* We trust the string here. If this interface is really extcap, the string is
+ * being checked immediatly before the dialog is being generated */
+ if (extcap_string.length() > 0)
+ {
+ /* this checks if configuration is required and not yet provided or saved via prefs */
+ if (extcap_has_configuration((const char *)(device_name.toStdString().c_str())))
+ {
+ emit showExtcapOptions(device_name, false);
+ return;
+ }
+ }
+ }
+}
+#endif
+
+void InterfaceFrame::updateStatistics(void)
+{
+ if (source_model_.rowCount() == 0)
+ return;
+
+#ifdef HAVE_LIBPCAP
+
+ for (int idx = 0; idx < source_model_.rowCount(); idx++)
+ {
+ QModelIndex selectIndex = info_model_.mapFromSource(proxy_model_.mapFromSource(source_model_.index(idx, 0)));
+
+ /* Proxy model has not masked out the interface */
+ if (selectIndex.isValid())
+ source_model_.updateStatistic(idx);
+ }
+#endif
+}
+
+void InterfaceFrame::showRunOnFile(void)
+{
+ ui->warningLabel->setText("Interfaces not loaded on startup (run on capture file). Go to Capture -> Refresh Interfaces to load.");
+}
+
+void InterfaceFrame::showContextMenu(QPoint pos)
+{
+ QMenu * ctx_menu = new QMenu(this);
+ // Work around QTBUG-106718. For some reason Qt::WA_DeleteOnClose +
+ // Qt::QueuedConnection don't work here.
+ QObject::connect(ctx_menu, &QMenu::triggered, ctx_menu, &QMenu::deleteLater);
+
+ ctx_menu->addAction(tr("Start capture"), this, [=] () {
+ QStringList ifaces;
+ QModelIndexList selIndices = ui->interfaceTree->selectionModel()->selectedIndexes();
+ foreach(QModelIndex idx, selIndices)
+ {
+ QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(idx));
+ if (realIndex.column() != IFTREE_COL_NAME)
+ realIndex = realIndex.sibling(realIndex.row(), IFTREE_COL_NAME);
+ QString name = realIndex.data(Qt::DisplayRole).toString();
+ if (! ifaces.contains(name))
+ ifaces << name;
+ }
+
+ startCapture(ifaces);
+ });
+
+ ctx_menu->addSeparator();
+
+ QModelIndex actIndex = ui->interfaceTree->indexAt(pos);
+ QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(actIndex));
+ bool isHidden = realIndex.sibling(realIndex.row(), IFTREE_COL_HIDDEN).data(Qt::UserRole).toBool();
+ QAction * hideAction = ctx_menu->addAction(tr("Hide Interface"), this, [=] () {
+ /* Attention! Only realIndex.row is a 1:1 correlation to all_ifaces */
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, realIndex.row());
+ device->hidden = ! device->hidden;
+ mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged);
+ });
+ hideAction->setCheckable(true);
+ hideAction->setChecked(isHidden);
+
+ ctx_menu->popup(ui->interfaceTree->mapToGlobal(pos));
+}
+
+void InterfaceFrame::on_warningLabel_linkActivated(const QString &link)
+{
+ if (link.compare(no_capture_link) == 0) {
+ recent.sys_warn_if_no_capture = FALSE;
+ resetInterfaceTreeDisplay();
+ } else {
+ QDesktopServices::openUrl(QUrl(link));
+ }
+}