2 * Display of interfaces, including their respective data, and the
3 * capability to filter interfaces by type
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
12 #include <ui_interface_frame.h>
14 #include "capture/capture_ifinfo.h"
17 #include "capture/capture-wpcap.h"
20 #include "ui/qt/interface_frame.h"
21 #include <ui/qt/simple_dialog.h>
22 #include <ui/qt/main_application.h>
24 #include <ui/qt/models/interface_tree_model.h>
25 #include <ui/qt/models/sparkline_delegate.h>
27 #include <ui/qt/utils/color_utils.h>
32 #include <ui/recent.h>
33 #include "capture_opts.h"
34 #include "ui/capture_globals.h"
35 #include <ui/iface_lists.h>
36 #include <wsutil/utf8_entities.h>
38 #include <QDesktopServices>
40 #include <QHBoxLayout>
41 #include <QItemSelection>
43 #include <QPushButton>
48 #include <epan/prefs.h>
50 #define BTN_IFTYPE_PROPERTY "ifType"
53 const int stat_update_interval_
= 1000; // ms
55 const char *no_capture_link
= "#no_capture";
57 static QMutex scan_mutex
;
59 InterfaceFrame::InterfaceFrame(QWidget
* parent
)
61 ui(new Ui::InterfaceFrame
)
62 , proxy_model_(Q_NULLPTR
)
63 , source_model_(Q_NULLPTR
)
67 #endif // HAVE_LIBPCAP
71 setStyleSheet(QString(
80 ui
->warningLabel
->hide();
83 ui
->interfaceTree
->setAttribute(Qt::WA_MacShowFocusRect
, false);
86 /* TODO: There must be a better way to do this */
87 ifTypeDescription
.insert(IF_WIRED
, tr("Wired"));
88 ifTypeDescription
.insert(IF_AIRPCAP
, tr("AirPCAP"));
89 ifTypeDescription
.insert(IF_PIPE
, tr("Pipe"));
90 ifTypeDescription
.insert(IF_STDIN
, tr("STDIN"));
91 ifTypeDescription
.insert(IF_BLUETOOTH
, tr("Bluetooth"));
92 ifTypeDescription
.insert(IF_WIRELESS
, tr("Wireless"));
93 ifTypeDescription
.insert(IF_DIALUP
, tr("Dial-Up"));
94 ifTypeDescription
.insert(IF_USB
, tr("USB"));
95 ifTypeDescription
.insert(IF_EXTCAP
, tr("External Capture"));
96 ifTypeDescription
.insert(IF_VIRTUAL
, tr ("Virtual"));
98 QList
<InterfaceTreeColumns
> columns
;
99 columns
.append(IFTREE_COL_EXTCAP
);
100 columns
.append(IFTREE_COL_DISPLAY_NAME
);
101 columns
.append(IFTREE_COL_STATS
);
102 proxy_model_
.setColumns(columns
);
103 proxy_model_
.setStoreOnChange(true);
104 proxy_model_
.setSortByActivity(true);
105 proxy_model_
.setSourceModel(&source_model_
);
107 info_model_
.setSourceModel(&proxy_model_
);
108 info_model_
.setColumn(static_cast<int>(columns
.indexOf(IFTREE_COL_STATS
)));
110 ui
->interfaceTree
->setModel(&info_model_
);
111 ui
->interfaceTree
->setSortingEnabled(true);
113 ui
->interfaceTree
->setItemDelegateForColumn(proxy_model_
.mapSourceToColumn(IFTREE_COL_STATS
), new SparkLineDelegate(this));
115 ui
->interfaceTree
->setContextMenuPolicy(Qt::CustomContextMenu
);
116 connect(ui
->interfaceTree
, SIGNAL(customContextMenuRequested(QPoint
)), this, SLOT(showContextMenu(QPoint
)));
118 connect(mainApp
, SIGNAL(appInitialized()), this, SLOT(interfaceListChanged()));
119 connect(mainApp
, SIGNAL(localInterfaceListChanged()), this, SLOT(interfaceListChanged()));
121 connect(ui
->interfaceTree
->selectionModel(), SIGNAL(selectionChanged(const QItemSelection
&, const QItemSelection
&)),
122 this, SLOT(interfaceTreeSelectionChanged(const QItemSelection
&, const QItemSelection
&)));
125 InterfaceFrame::~InterfaceFrame()
130 QMenu
* InterfaceFrame::getSelectionMenu()
132 QMenu
* contextMenu
= new QMenu(this);
133 QList
<int> typesDisplayed
= proxy_model_
.typesDisplayed();
135 QMap
<int, QString
>::const_iterator it
= ifTypeDescription
.constBegin();
136 while (it
!= ifTypeDescription
.constEnd())
138 int ifType
= it
.key();
140 if (typesDisplayed
.contains(ifType
))
142 QAction
*endp_action
= new QAction(it
.value(), this);
143 endp_action
->setData(QVariant::fromValue(ifType
));
144 endp_action
->setCheckable(true);
145 endp_action
->setChecked(proxy_model_
.isInterfaceTypeShown(ifType
));
146 connect(endp_action
, SIGNAL(triggered()), this, SLOT(triggeredIfTypeButton()));
147 contextMenu
->addAction(endp_action
);
152 #ifdef HAVE_PCAP_REMOTE
153 if (proxy_model_
.remoteInterfacesExist())
155 QAction
* toggleRemoteAction
= new QAction(tr("Remote interfaces"), this);
156 toggleRemoteAction
->setCheckable(true);
157 toggleRemoteAction
->setChecked(proxy_model_
.remoteDisplay());
158 connect(toggleRemoteAction
, SIGNAL(triggered()), this, SLOT(toggleRemoteInterfaces()));
159 contextMenu
->addAction(toggleRemoteAction
);
163 contextMenu
->addSeparator();
164 QAction
* toggleHideAction
= new QAction(tr("Show hidden interfaces"), this);
165 toggleHideAction
->setCheckable(true);
166 toggleHideAction
->setChecked(! proxy_model_
.filterHidden());
167 connect(toggleHideAction
, SIGNAL(triggered()), this, SLOT(toggleHiddenInterfaces()));
168 contextMenu
->addAction(toggleHideAction
);
173 int InterfaceFrame::interfacesHidden()
175 return proxy_model_
.interfacesHidden();
178 int InterfaceFrame::interfacesPresent()
180 return source_model_
.rowCount() - proxy_model_
.interfacesHidden();
183 void InterfaceFrame::ensureSelectedInterface()
186 if (interfacesPresent() < 1) return;
188 if (source_model_
.selectedDevices().count() < 1) {
189 QModelIndex first_idx
= info_model_
.mapFromSource(proxy_model_
.index(0, 0));
190 ui
->interfaceTree
->setCurrentIndex(first_idx
);
193 ui
->interfaceTree
->setFocus();
197 void InterfaceFrame::hideEvent(QHideEvent
*) {
201 source_model_
.stopStatistic();
202 #endif // HAVE_LIBPCAP
205 void InterfaceFrame::showEvent(QShowEvent
*) {
209 stat_timer_
->start(stat_update_interval_
);
210 #endif // HAVE_LIBPCAP
214 void InterfaceFrame::scanLocalInterfaces(GList
*filter_list
)
216 GList
*if_list
= NULL
;
217 if (scan_mutex
.tryLock()) {
219 source_model_
.stopStatistic();
220 if_stat_cache_t
* stat_cache
= capture_interface_stat_start(&global_capture_opts
, &if_list
);
221 source_model_
.setCache(stat_cache
);
223 mainApp
->setInterfaceList(if_list
);
224 free_interface_list(if_list
);
225 scan_local_interfaces_filtered(filter_list
, main_window_update
);
226 mainApp
->emitAppSignal(MainApplication::LocalInterfacesChanged
);
229 qDebug() << "scan mutex locked, can't scan interfaces";
232 #endif // HAVE_LIBPCAP
234 void InterfaceFrame::actionButton_toggled(bool checked
)
236 QVariant ifType
= sender()->property(BTN_IFTYPE_PROPERTY
);
237 if (ifType
.isValid())
239 proxy_model_
.setInterfaceTypeVisible(ifType
.toInt(), checked
);
242 resetInterfaceTreeDisplay();
245 void InterfaceFrame::triggeredIfTypeButton()
247 QAction
*sender
= qobject_cast
<QAction
*>(QObject::sender());
250 int ifType
= sender
->data().value
<int>();
251 proxy_model_
.toggleTypeVisibility(ifType
);
253 resetInterfaceTreeDisplay();
254 emit
typeSelectionChanged();
258 void InterfaceFrame::interfaceListChanged()
260 info_model_
.clearInfos();
261 if (prefs
.capture_no_extcap
)
262 info_model_
.appendInfo(tr("External capture interfaces disabled."));
264 resetInterfaceTreeDisplay();
265 // Ensure that device selection is consistent with the displayed selection.
266 updateSelectedInterfaces();
271 stat_timer_
= new QTimer(this);
272 connect(stat_timer_
, SIGNAL(timeout()), this, SLOT(updateStatistics()));
273 stat_timer_
->start(stat_update_interval_
);
278 void InterfaceFrame::toggleHiddenInterfaces()
280 source_model_
.interfaceListChanged();
281 proxy_model_
.toggleFilterHidden();
283 emit
typeSelectionChanged();
286 #ifdef HAVE_PCAP_REMOTE
287 void InterfaceFrame::toggleRemoteInterfaces()
289 proxy_model_
.toggleRemoteDisplay();
290 emit
typeSelectionChanged();
294 void InterfaceFrame::resetInterfaceTreeDisplay()
296 ui
->warningLabel
->hide();
297 ui
->warningLabel
->clear();
299 ui
->warningLabel
->setStyleSheet(QString(
301 " border-radius: 0.5em;"
303 " margin-bottom: 0.25em;"
304 // We might want to transition this to normal colors this after a timeout.
305 " background-color: %2;"
307 ).arg(ColorUtils::warningBackground().name()));
312 ui
->warningLabel
->setText(tr(
314 "Local interfaces are unavailable because no packet capture driver is installed."
316 "You can fix this by installing <a href=\"https://npcap.com/\">Npcap</a>."
318 } else if (!npf_sys_is_running()) {
319 ui
->warningLabel
->setText(tr(
321 "Local interfaces are unavailable because the packet capture driver isn't loaded."
323 "You can fix this by running <pre>net start npcap</pre> if you have Npcap installed"
324 " or <pre>net start npf</pre> if you have WinPcap installed."
325 " Both commands must be run as Administrator."
330 if (!haveLocalCapturePermissions())
334 // NOTE: if you change this text, you must also change the
335 // definition of PLATFORM_PERMISSIONS_SUGGESTION that is
336 // used if __APPLE__ is defined, so that it reflects the
339 QString install_chmodbpf_path
= mainApp
->applicationDirPath() + "/../Resources/Extras/Install ChmodBPF.pkg";
340 ui
->warningLabel
->setText(tr(
342 "You don't have permission to capture on local interfaces."
344 "You can fix this by <a href=\"file://%1\">installing ChmodBPF</a>."
346 .arg(install_chmodbpf_path
));
349 // XXX - should this give similar platform-dependent recommendations,
350 // just as dumpcap gives platform-dependent recommendations in its
351 // PLATFORM_PERMISSIONS_SUGGESTION #define?
353 ui
->warningLabel
->setText(tr("You don't have permission to capture on local interfaces."));
357 if (proxy_model_
.rowCount() == 0)
359 ui
->warningLabel
->setText(tr("No interfaces found."));
360 ui
->warningLabel
->setText(proxy_model_
.interfaceError());
361 if (prefs
.capture_no_interface_load
) {
362 ui
->warningLabel
->setText(tr("Interfaces not loaded (due to preference). Go to Capture " UTF8_RIGHTWARDS_ARROW
" Refresh Interfaces to load."));
366 // XXX Should we have a separate recent pref for each message?
367 if (!ui
->warningLabel
->text().isEmpty() && recent
.sys_warn_if_no_capture
)
369 QString warning_text
= ui
->warningLabel
->text();
370 warning_text
.append(QStringLiteral("<p><a href=\"%1\">%2</a></p>")
371 .arg(no_capture_link
)
372 .arg(SimpleDialog::dontShowThisAgain()));
373 ui
->warningLabel
->setText(warning_text
);
375 ui
->warningLabel
->show();
377 #endif // HAVE_LIBPCAP
379 if (proxy_model_
.rowCount() > 0)
381 ui
->interfaceTree
->show();
382 ui
->interfaceTree
->resizeColumnToContents(proxy_model_
.mapSourceToColumn(IFTREE_COL_EXTCAP
));
383 ui
->interfaceTree
->resizeColumnToContents(proxy_model_
.mapSourceToColumn(IFTREE_COL_DISPLAY_NAME
));
384 ui
->interfaceTree
->resizeColumnToContents(proxy_model_
.mapSourceToColumn(IFTREE_COL_STATS
));
388 ui
->interfaceTree
->hide();
392 // XXX Should this be in capture/capture-pcap-util.[ch]?
393 bool InterfaceFrame::haveLocalCapturePermissions() const
396 QFileInfo bpf0_fi
= QFileInfo("/dev/bpf0");
397 return bpf0_fi
.isReadable() && bpf0_fi
.isWritable();
399 // XXX Add checks for other platforms.
404 void InterfaceFrame::updateSelectedInterfaces()
406 if (source_model_
.rowCount() == 0)
409 QItemSelection sourceSelection
= source_model_
.selectedDevices();
410 QItemSelection mySelection
= info_model_
.mapSelectionFromSource(proxy_model_
.mapSelectionFromSource(sourceSelection
));
412 ui
->interfaceTree
->selectionModel()->clearSelection();
413 ui
->interfaceTree
->selectionModel()->select(mySelection
, QItemSelectionModel::SelectCurrent
);
417 void InterfaceFrame::interfaceTreeSelectionChanged(const QItemSelection
& selected
, const QItemSelection
& deselected
)
419 if (selected
.count() == 0 && deselected
.count() == 0)
421 if (source_model_
.rowCount() == 0)
425 /* Take all selected interfaces, not just the newly ones */
426 QItemSelection allSelected
= ui
->interfaceTree
->selectionModel()->selection();
427 QItemSelection sourceSelection
= proxy_model_
.mapSelectionToSource(info_model_
.mapSelectionToSource(allSelected
));
429 if (source_model_
.updateSelectedDevices(sourceSelection
))
430 emit
itemSelectionChanged();
434 void InterfaceFrame::on_interfaceTree_doubleClicked(const QModelIndex
&index
)
436 QModelIndex realIndex
= proxy_model_
.mapToSource(info_model_
.mapToSource(index
));
438 if (! realIndex
.isValid())
441 QStringList interfaces
;
445 QString device_name
= source_model_
.getColumnContent(realIndex
.row(), IFTREE_COL_NAME
).toString();
446 QString extcap_string
= source_model_
.getColumnContent(realIndex
.row(), IFTREE_COL_EXTCAP_PATH
).toString();
448 interfaces
<< device_name
;
450 /* We trust the string here. If this interface is really extcap, the string is
451 * being checked immediately before the dialog is being generated */
452 if (extcap_string
.length() > 0)
454 /* this checks if configuration is required and not yet provided or saved via prefs */
455 if (extcap_requires_configuration((const char *)(device_name
.toStdString().c_str())))
457 emit
showExtcapOptions(device_name
, true);
463 // Start capture for all columns except the first one with extcap
464 if (IFTREE_COL_EXTCAP
!= realIndex
.column()) {
465 startCapture(interfaces
);
470 void InterfaceFrame::on_interfaceTree_clicked(const QModelIndex
&index
)
472 if (index
.column() == 0)
474 QModelIndex realIndex
= proxy_model_
.mapToSource(info_model_
.mapToSource(index
));
476 if (! realIndex
.isValid())
479 QString device_name
= source_model_
.getColumnContent(realIndex
.row(), IFTREE_COL_NAME
).toString();
480 QString extcap_string
= source_model_
.getColumnContent(realIndex
.row(), IFTREE_COL_EXTCAP_PATH
).toString();
482 /* We trust the string here. If this interface is really extcap, the string is
483 * being checked immediately before the dialog is being generated */
484 if (extcap_string
.length() > 0)
486 /* this checks if configuration is required and not yet provided or saved via prefs */
487 if (extcap_has_configuration((const char *)(device_name
.toStdString().c_str())))
489 emit
showExtcapOptions(device_name
, false);
497 void InterfaceFrame::updateStatistics(void)
499 if (source_model_
.rowCount() == 0)
504 for (int idx
= 0; idx
< source_model_
.rowCount(); idx
++)
506 QModelIndex selectIndex
= info_model_
.mapFromSource(proxy_model_
.mapFromSource(source_model_
.index(idx
, 0)));
508 /* Proxy model has not masked out the interface */
509 if (selectIndex
.isValid())
510 source_model_
.updateStatistic(idx
);
515 void InterfaceFrame::showRunOnFile(void)
517 ui
->warningLabel
->setText("Interfaces not loaded on startup (run on capture file). Go to Capture -> Refresh Interfaces to load.");
520 void InterfaceFrame::showContextMenu(QPoint pos
)
522 QMenu
* ctx_menu
= new QMenu(this);
523 // Work around QTBUG-106718. For some reason Qt::WA_DeleteOnClose +
524 // Qt::QueuedConnection don't work here.
525 QObject::connect(ctx_menu
, &QMenu::triggered
, ctx_menu
, &QMenu::deleteLater
);
527 ctx_menu
->addAction(tr("Start capture"), this, [=] () {
529 QModelIndexList selIndices
= ui
->interfaceTree
->selectionModel()->selectedIndexes();
530 foreach(QModelIndex idx
, selIndices
)
532 QModelIndex realIndex
= proxy_model_
.mapToSource(info_model_
.mapToSource(idx
));
533 if (realIndex
.column() != IFTREE_COL_NAME
)
534 realIndex
= realIndex
.sibling(realIndex
.row(), IFTREE_COL_NAME
);
535 QString name
= realIndex
.data(Qt::DisplayRole
).toString();
536 if (! ifaces
.contains(name
))
540 startCapture(ifaces
);
543 ctx_menu
->addSeparator();
545 QModelIndex actIndex
= ui
->interfaceTree
->indexAt(pos
);
546 QModelIndex realIndex
= proxy_model_
.mapToSource(info_model_
.mapToSource(actIndex
));
547 bool isHidden
= realIndex
.sibling(realIndex
.row(), IFTREE_COL_HIDDEN
).data(Qt::UserRole
).toBool();
548 QAction
* hideAction
= ctx_menu
->addAction(tr("Hide Interface"), this, [=] () {
549 /* Attention! Only realIndex.row is a 1:1 correlation to all_ifaces */
550 interface_t
*device
= &g_array_index(global_capture_opts
.all_ifaces
, interface_t
, realIndex
.row());
551 device
->hidden
= ! device
->hidden
;
552 mainApp
->emitAppSignal(MainApplication::LocalInterfacesChanged
);
554 hideAction
->setCheckable(true);
555 hideAction
->setChecked(isHidden
);
557 ctx_menu
->popup(ui
->interfaceTree
->mapToGlobal(pos
));
560 void InterfaceFrame::on_warningLabel_linkActivated(const QString
&link
)
562 if (link
.compare(no_capture_link
) == 0) {
563 recent
.sys_warn_if_no_capture
= false;
564 resetInterfaceTreeDisplay();
566 QDesktopServices::openUrl(QUrl(link
));