1 /* extcap_options_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
12 #include <extcap_options_dialog.h>
13 #include <ui_extcap_options_dialog.h>
15 #include <main_application.h>
17 #include <QMessageBox>
19 #include <QHBoxLayout>
20 #include <QVBoxLayout>
21 #include <QGridLayout>
23 #include <QDesktopServices>
26 #include "ringbuffer.h"
27 #include "ui/capture_ui_utils.h"
28 #include "ui/capture_globals.h"
29 #include "ui/iface_lists.h"
31 #include "ui/ws_ui_util.h"
33 #include <wsutil/utf8_entities.h>
36 #include <epan/addr_resolv.h>
37 #include <wsutil/filesystem.h>
40 #include <extcap_parser.h>
42 #include <ui/qt/utils/qt_ui_utils.h>
44 #include <epan/prefs.h>
45 #include <ui/preference_utils.h>
47 #include <ui/qt/main_application.h>
48 #include <ui/qt/utils/stock_icon.h>
49 #include <ui/qt/utils/variant_pointer.h>
51 #include <ui/qt/extcap_argument.h>
52 #include <ui/qt/extcap_argument_file.h>
53 #include <ui/qt/extcap_argument_multiselect.h>
55 ExtcapOptionsDialog::ExtcapOptionsDialog(bool startCaptureOnClose
, QWidget
*parent
) :
57 ui(new Ui::ExtcapOptionsDialog
),
60 defaultValueIcon_(StockIcon("x-reset"))
64 setWindowTitle(mainApp
->windowTitleString(tr("Interface Options")));
66 ui
->checkSaveOnStart
->setCheckState(prefs
.extcap_save_on_start
? Qt::Checked
: Qt::Unchecked
);
68 ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setText(tr("Start"));
69 if (startCaptureOnClose
) {
70 // This dialog was spawned because the user wanted to start a capture
71 // immediately but a mandatory parameter was not configured.
72 ui
->buttonBox
->button(QDialogButtonBox::Save
)->hide();
76 ExtcapOptionsDialog
* ExtcapOptionsDialog::createForDevice(QString
&dev_name
, bool startCaptureOnClose
, QWidget
*parent
)
79 ExtcapOptionsDialog
* resultDialog
= NULL
;
80 bool dev_found
= false;
83 if (dev_name
.length() == 0)
86 for (if_idx
= 0; if_idx
< global_capture_opts
.all_ifaces
->len
; if_idx
++)
88 device
= &g_array_index(global_capture_opts
.all_ifaces
, interface_t
, if_idx
);
89 if (dev_name
.compare(QString(device
->name
)) == 0 && device
->if_info
.type
== IF_EXTCAP
)
99 resultDialog
= new ExtcapOptionsDialog(startCaptureOnClose
, parent
);
100 resultDialog
->device_name
= QString(dev_name
);
101 resultDialog
->device_idx
= if_idx
;
103 resultDialog
->setWindowTitle(mainApp
->windowTitleString(tr("Interface Options") + ": " + device
->display_name
));
105 resultDialog
->updateWidgets();
107 /* mark required fields */
108 resultDialog
->anyValueChanged();
114 ExtcapOptionsDialog::~ExtcapOptionsDialog()
119 void ExtcapOptionsDialog::anyValueChanged()
121 bool allowStart
= true;
123 ExtcapArgumentList::const_iterator iter
;
125 /* All arguments are being iterated, to ensure, that any error handling catches all arguments */
126 for (iter
= extcapArguments
.constBegin(); iter
!= extcapArguments
.constEnd(); ++iter
)
128 /* The dynamic casts are necessary, because we come here using the Signal/Slot system
129 * of Qt, and -in short- Q_OBJECT classes cannot be multiple inherited. Another possibility
130 * would be to use Q_INTERFACE, but this causes way more nightmares, and we really just
131 * need here an explicit cast for the check functionality */
132 if (dynamic_cast<ExtArgBool
*>((*iter
)) != NULL
)
134 if (! ((ExtArgBool
*)*iter
)->isValid())
137 else if (dynamic_cast<ExtArgRadio
*>((*iter
)) != NULL
)
139 if (! ((ExtArgRadio
*)*iter
)->isValid())
142 else if (dynamic_cast<ExtArgSelector
*>((*iter
)) != NULL
)
144 if (! ((ExtArgSelector
*)*iter
)->isValid())
147 else if (dynamic_cast<ExtArgMultiSelect
*>((*iter
)) != NULL
)
149 if (! ((ExtArgMultiSelect
*)*iter
)->isValid())
152 else if (dynamic_cast<ExtcapArgumentFileSelection
*>((*iter
)) != NULL
)
154 if (! ((ExtcapArgumentFileSelection
*)*iter
)->isValid())
157 else if (dynamic_cast<ExtArgNumber
*>((*iter
)) != NULL
)
159 if (! ((ExtArgNumber
*)*iter
)->isValid())
162 else if (dynamic_cast<ExtArgText
*>((*iter
)) != NULL
)
164 if (! ((ExtArgText
*)*iter
)->isValid())
167 else if (dynamic_cast<ExtArgTimestamp
*>((*iter
)) != NULL
)
169 if (! ((ExtArgTimestamp
*)*iter
)->isValid())
173 if (! (*iter
)->isValid())
177 ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setEnabled(allowStart
);
180 void ExtcapOptionsDialog::loadArguments()
182 GList
* arguments
= Q_NULLPTR
, * walker
= Q_NULLPTR
, * item
= Q_NULLPTR
;
183 ExtcapArgument
* argument
= Q_NULLPTR
;
185 if (device_name
.length() == 0 )
188 extcapArguments
.clear();
190 arguments
= g_list_first(extcap_get_if_configuration(device_name
.toUtf8().constData()));
192 ExtcapArgumentList required
;
193 ExtcapArgumentList optional
;
196 while (walker
!= Q_NULLPTR
)
198 item
= g_list_first(gxx_list_data(GList
*, walker
));
199 while (item
!= Q_NULLPTR
)
201 argument
= ExtcapArgument::create(gxx_list_data(extcap_arg
*, item
), this);
202 if (argument
!= Q_NULLPTR
)
204 if (argument
->isRequired())
205 required
<< argument
;
207 optional
<< argument
;
212 walker
= gxx_list_next(walker
);
215 if (required
.length() > 0)
216 extcapArguments
<< required
;
218 if (optional
.length() > 0)
219 extcapArguments
<< optional
;
221 /* argument items are now owned by ExtcapArgument. Only free the lists */
222 extcap_free_if_configuration(arguments
, false);
225 void ExtcapOptionsDialog::updateWidgets()
227 QWidget
* lblWidget
= NULL
, *editWidget
= NULL
;
228 ExtcapArgument
* argument
= NULL
;
229 bool allowStart
= true;
231 unsigned int counter
= 0;
233 if (device_name
.length() == 0 )
236 /* find existing layout */
237 if (ui
->verticalLayout
->children().count() > 0)
239 ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setEnabled(false);
240 QWidget
* item
= ui
->verticalLayout
->itemAt(0)->widget();
243 ui
->verticalLayout
->removeItem(ui
->verticalLayout
->itemAt(0));
248 QHash
<QString
, QWidget
*> layouts
;
250 /* Load all extcap arguments */
253 /* exit if no arguments have been found. This is a precaution, it should
254 * never happen, that this dialog get's called without any arguments */
255 if (extcapArguments
.count() == 0)
257 ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setEnabled(true);
260 ui
->checkSaveOnStart
->setText(tr("Save parameter(s) on capture start", "", static_cast<int>(extcapArguments
.count())));
262 QStringList groupKeys
;
263 QString
defaultKeyName(tr("Default"));
264 /* QMap sorts keys, therefore the groups are sorted by appearance */
265 QMap
<int, QString
> groups
;
267 /* Look for all necessary tabs */
268 ExtcapArgumentList::iterator iter
= extcapArguments
.begin();
269 while (iter
!= extcapArguments
.end())
271 argument
= (ExtcapArgument
*)(*iter
);
272 QString groupKey
= argument
->group();
273 if (groupKey
.length() > 0)
275 if (! groups
.values().contains(groupKey
))
276 groups
.insert(argument
->argNr(), groupKey
);
278 else if (! groups
.keys().contains(0))
280 groups
.insert(0, defaultKeyName
);
281 groupKey
= defaultKeyName
;
284 if (! layouts
.keys().contains(groupKey
))
286 QWidget
* tabWidget
= new QWidget(this);
287 QGridLayout
* tabLayout
= new QGridLayout(tabWidget
);
288 tabWidget
->setLayout(tabLayout
);
290 layouts
.insert(groupKey
, tabWidget
);
295 groupKeys
<< groups
.values();
297 /* Iterate over all arguments and do the following:
298 * 1. create the label for each element
299 * 2. create an editor for each element
300 * 3. add both to the layout for the tab widget
302 iter
= extcapArguments
.begin();
303 while (iter
!= extcapArguments
.end())
305 argument
= (ExtcapArgument
*)(*iter
);
306 QString groupKey
= defaultKeyName
;
307 if (argument
->group().length() > 0)
308 groupKey
= argument
->group();
310 /* Skip non-assigned group keys, this happens if the configuration of the extcap is faulty */
311 if (! layouts
.keys().contains(groupKey
))
317 QGridLayout
* layout
= ((QGridLayout
*)layouts
[groupKey
]->layout());
318 lblWidget
= argument
->createLabel((QWidget
*)this);
319 if (lblWidget
!= NULL
)
321 layout
->addWidget(lblWidget
, counter
, 0, Qt::AlignVCenter
);
322 editWidget
= argument
->createEditor((QWidget
*) this);
323 if (editWidget
!= NULL
)
325 editWidget
->setProperty("extcap", VariantPointer
<ExtcapArgument
>::asQVariant(argument
));
326 layout
->addWidget(editWidget
, counter
, 1, Qt::AlignVCenter
);
328 if (argument
->isSetDefaultValueSupported())
330 QPushButton
*button
= new QPushButton(defaultValueIcon_
,"");
331 button
->setToolTip(tr("Restore default value of the item"));
332 layout
->addWidget(button
, counter
, 2, Qt::AlignVCenter
);
333 connect(button
, SIGNAL(clicked()), argument
, SLOT(setDefaultValue()));
337 if (argument
->isRequired() && ! argument
->isValid())
340 connect(argument
, &ExtcapArgument::valueChanged
, this, &ExtcapOptionsDialog::anyValueChanged
);
349 setStyleSheet ("QLabel[isRequired=\"true\"] { font-weight: bold; } ");
351 ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setEnabled(allowStart
);
353 QWidget
* mainWidget
= Q_NULLPTR
;
355 /* We should never display the dialog, if no settings are present */
356 Q_ASSERT(layouts
.count() > 0);
358 if (layouts
.count() > 1)
360 QTabWidget
* tabs
= new QTabWidget(this);
361 foreach (QString key
, groupKeys
)
363 layouts
[key
]->layout()->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum
, QSizePolicy::MinimumExpanding
));
364 tabs
->addTab(layouts
[key
], key
);
367 tabs
->setCurrentIndex(0);
370 else if (layouts
.count() == 1)
371 mainWidget
= layouts
[layouts
.keys().at(0)];
373 ui
->verticalLayout
->addWidget(mainWidget
);
374 ui
->verticalLayout
->addSpacerItem(new QSpacerItem(20, 100, QSizePolicy::Minimum
, QSizePolicy::Expanding
));
378 QList
<QString
> keys
= layouts
.keys();
379 foreach (QString key
, keys
)
380 delete(layouts
[key
]);
384 void ExtcapOptionsDialog::on_buttonBox_helpRequested()
387 QString interface_help
= NULL
;
389 device
= &g_array_index(global_capture_opts
.all_ifaces
, interface_t
, device_idx
);
390 interface_help
= QString(extcap_get_help_for_ifname(device
->name
));
391 /* The extcap interface didn't provide an help. Let's go with the default */
392 if (interface_help
.isEmpty()) {
393 mainApp
->helpTopicAction(HELP_EXTCAP_OPTIONS_DIALOG
);
397 QUrl
help_url(interface_help
);
399 /* Check the existence for a local file */
400 if (help_url
.isLocalFile()) {
401 QString local_path
= help_url
.toLocalFile();
402 QFileInfo
help_file(local_path
);
403 if (!help_file
.exists()) {
404 QMessageBox::warning(this, tr("Extcap Help cannot be found"),
405 tr("The help for the extcap interface %1 cannot be found. Given file: %2")
406 .arg(device
->name
).arg(QDir::toNativeSeparators(local_path
)),
412 /* We have an actual url or an existing local file. Let's open it. */
413 QDesktopServices::openUrl(help_url
);
416 bool ExtcapOptionsDialog::saveOptionToCaptureInfo()
418 GHashTable
* ret_args
;
421 device
= &g_array_index(global_capture_opts
.all_ifaces
, interface_t
, device_idx
);
422 ret_args
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
424 ExtcapArgumentList::const_iterator iter
;
426 for (iter
= extcapArguments
.constBegin(); iter
!= extcapArguments
.constEnd(); ++iter
)
428 QString call
= (*iter
)->call();
429 QString value
= (*iter
)->value();
430 QString prefValue
= (*iter
)->prefValue();
432 if ((*iter
)->argument()->arg_type
!= EXTCAP_ARG_BOOLFLAG
&& value
.length() == 0)
435 if (call
.length() <= 0) {
439 if (value
.compare((*iter
)->defaultValue()) == 0) {
440 // What _does_ required and also has a default mean (And how is
441 // it different, if at all, from has a default and also placeholder
442 // text)? Will the extcap use the default if we don't pass the
443 // argument (so it's not really required)?
444 // To be safe we can pass the default explicitly.
445 if (!(*iter
)->isRequired())
449 char * call_string
= qstring_strdup(call
);
450 char * value_string
= NULL
;
451 if (value
.length() > 0)
452 value_string
= qstring_strdup(value
);
454 g_hash_table_insert(ret_args
, call_string
, value_string
);
457 if (device
->external_cap_args_settings
!= NULL
)
458 g_hash_table_unref(device
->external_cap_args_settings
);
459 device
->external_cap_args_settings
= ret_args
;
463 void ExtcapOptionsDialog::on_buttonBox_clicked(QAbstractButton
*button
)
465 /* Only the save button has the ActionRole */
466 switch (ui
->buttonBox
->buttonRole(button
)) {
467 case QDialogButtonBox::ResetRole
:
470 case QDialogButtonBox::RejectRole
:
471 case QDialogButtonBox::DestructiveRole
:
472 /* entries are only saved if saveOptionToCaptureInfo() is called,
476 case QDialogButtonBox::AcceptRole
:
477 if (saveOptionToCaptureInfo()) {
478 /* Starting a new capture with those values */
479 prefs
.extcap_save_on_start
= ui
->checkSaveOnStart
->checkState() == Qt::Checked
;
481 /* XXX - If extcap_save_on_start is the only preference that has
482 * changed, or if it changed from true to false, we should write
483 * out a new preference file with its new value, but don't.
485 if (ui
->buttonBox
->standardButton(button
) == QDialogButtonBox::Save
) {
487 /* Reject the dialog, because we don't want to start a capture. */
491 if (prefs
.extcap_save_on_start
) {
503 void ExtcapOptionsDialog::resetValues()
505 int count
= ui
->verticalLayout
->count();
508 QList
<QLayout
*> layouts
;
510 /* Find all layouts */
511 if (qobject_cast
<QTabWidget
*>(ui
->verticalLayout
->itemAt(0)->widget()))
513 QTabWidget
* tabs
= qobject_cast
<QTabWidget
*>(ui
->verticalLayout
->itemAt(0)->widget());
514 for (int cnt
= 0; cnt
< tabs
->count(); cnt
++)
516 layouts
.append(tabs
->widget(cnt
)->layout());
520 layouts
.append(ui
->verticalLayout
->itemAt(0)->layout());
522 /* Loop over all layouts */
523 for (int cnt
= 0; cnt
< layouts
.count(); cnt
++)
525 QGridLayout
* layout
= qobject_cast
<QGridLayout
*>(layouts
.at(cnt
));
529 /* Loop over all widgets in column 1 on layout */
530 for (int row
= 0; row
< layout
->rowCount(); row
++)
532 QWidget
* child
= Q_NULLPTR
;
533 if (layout
->itemAtPosition(row
, 1))
534 child
= qobject_cast
<QWidget
*>(layout
->itemAtPosition(row
, 1)->widget());
538 /* Don't need labels, the edit widget contains the extcapargument property value */
539 ExtcapArgument
* arg
= 0;
540 QVariant prop
= child
->property("extcap");
544 arg
= VariantPointer
<ExtcapArgument
>::asPtr(prop
);
546 /* value<> can fail */
549 arg
->setDefaultValue();
557 /* Values are stored when dialog is committed, just check validity */
562 GHashTable
*ExtcapOptionsDialog::getArgumentSettings(bool useCallsAsKey
, bool includeEmptyValues
)
564 GHashTable
* entries
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
565 ExtcapArgumentList::const_iterator iter
;
569 /* All arguments are being iterated, to ensure, that any error handling catches all arguments */
570 for (iter
= extcapArguments
.constBegin(); iter
!= extcapArguments
.constEnd(); ++iter
)
572 ExtcapArgument
* argument
= (ExtcapArgument
*)(*iter
);
573 bool isBoolflag
= false;
575 /* The dynamic casts are necessary, because we come here using the Signal/Slot system
576 * of Qt, and -in short- Q_OBJECT classes cannot be multiple inherited. Another possibility
577 * would be to use Q_INTERFACE, but this causes way more nightmares, and we really just
578 * need here an explicit cast for the check functionality */
579 if (dynamic_cast<ExtArgBool
*>((*iter
)) != NULL
)
581 value
= ((ExtArgBool
*)*iter
)->prefValue();
582 // For boolflag there should be no value
583 if ((*iter
)->argument()->arg_type
!= EXTCAP_ARG_BOOLFLAG
)
586 else if (dynamic_cast<ExtArgRadio
*>((*iter
)) != NULL
)
588 value
= ((ExtArgRadio
*)*iter
)->prefValue();
590 else if (dynamic_cast<ExtArgSelector
*>((*iter
)) != NULL
)
592 value
= ((ExtArgSelector
*)*iter
)->prefValue();
594 else if (dynamic_cast<ExtArgMultiSelect
*>((*iter
)) != NULL
)
596 value
= ((ExtArgMultiSelect
*)*iter
)->prefValue();
598 else if (dynamic_cast<ExtcapArgumentFileSelection
*>((*iter
)) != NULL
)
600 value
= ((ExtcapArgumentFileSelection
*)*iter
)->prefValue();
602 else if (dynamic_cast<ExtArgNumber
*>((*iter
)) != NULL
)
604 value
= ((ExtArgNumber
*)*iter
)->prefValue();
606 else if (dynamic_cast<ExtArgText
*>((*iter
)) != NULL
)
608 value
= ((ExtArgText
*)*iter
)->prefValue();
610 else if (dynamic_cast<ExtArgTimestamp
*>((*iter
)) != NULL
)
612 value
= ((ExtArgTimestamp
*)*iter
)->prefValue();
615 value
= (*iter
)->prefValue();
617 QString key
= argument
->prefKey(device_name
);
619 key
= argument
->call();
621 if ((key
.length() > 0) && (includeEmptyValues
|| isBoolflag
|| value
.length() > 0) )
623 char * val
= qstring_strdup(value
);
625 g_hash_table_insert(entries
, qstring_strdup(key
), val
);
632 void ExtcapOptionsDialog::storeValues()
634 GHashTable
* entries
= getArgumentSettings();
636 if (g_hash_table_size(entries
) > 0)
638 if (prefs_store_ext_multiple("extcap", entries
))
639 mainApp
->emitAppSignal(MainApplication::PreferencesChanged
);
643 g_hash_table_unref(entries
);
646 ExtcapValueList
ExtcapOptionsDialog::loadValuesFor(int argNum
, QString argumentName
, QString parent
)
648 ExtcapValueList elements
;
649 GList
* walker
= 0, * values
= 0;
652 QList
<QWidget
*> children
= findChildren
<QWidget
*>();
653 foreach (QWidget
* child
, children
)
654 child
->setEnabled(false);
656 QString argcall
= argumentName
;
657 if (argcall
.startsWith("--"))
658 argcall
= argcall
.right(argcall
.size()-2);
660 GHashTable
* entries
= getArgumentSettings(true, false);
662 values
= extcap_get_if_configuration_values(this->device_name
.toStdString().c_str(), argcall
.toStdString().c_str(), entries
);
664 for (walker
= g_list_first((GList
*)(values
)); walker
!= NULL
; walker
= walker
->next
)
666 v
= (extcap_value
*) walker
->data
;
667 if (v
== NULL
|| v
->display
== NULL
|| v
->call
== NULL
)
670 /* Only accept values for this argument */
671 if (v
->arg_num
!= argNum
)
674 QString valParent
= QString().fromUtf8(v
->parent
);
676 if (parent
.compare(valParent
) == 0)
679 QString display
= QString().fromUtf8(v
->display
);
680 QString call
= QString().fromUtf8(v
->call
);
682 ExtcapValue element
= ExtcapValue(display
, call
,
683 v
->enabled
== true, v
->is_default
== true);
686 /* TODO: Disabled due to wrong parent handling. It leads to an infinite loop for now. To implement this properly, other things
687 will be needed, like new arguments for setting the parent in the call to the extcap utility*/
689 element
.setChildren(this->loadValuesFor(argumentName
, call
));
692 elements
.append(element
);
696 foreach (QWidget
* child
, children
)
697 child
->setEnabled(true);