623 lines
19 KiB
C++
623 lines
19 KiB
C++
/* interface_tree_cache_model.cpp
|
|
* Model caching interface changes before sending them to global storage
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* Copyright 1998 Gerald Combs
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
|
|
#include "ui/qt/interface_tree_cache_model.h"
|
|
|
|
#include "glib.h"
|
|
|
|
#include "epan/prefs.h"
|
|
|
|
#include "qt_ui_utils.h"
|
|
#include "ui/capture_globals.h"
|
|
#include "wsutil/utf8_entities.h"
|
|
|
|
#include "wiretap/wtap.h"
|
|
|
|
#include "wireshark_application.h"
|
|
|
|
InterfaceTreeCacheModel::InterfaceTreeCacheModel(QObject *parent) :
|
|
QIdentityProxyModel(parent)
|
|
{
|
|
/* ATTENTION: This cache model is not intended to be used with anything
|
|
* else then InterfaceTreeModel, and will break with anything else
|
|
* leading to unintended results. */
|
|
sourceModel = new InterfaceTreeModel(parent);
|
|
|
|
QIdentityProxyModel::setSourceModel(sourceModel);
|
|
storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>();
|
|
|
|
checkableColumns << IFTREE_COL_HIDDEN << IFTREE_COL_PROMISCUOUSMODE;
|
|
#ifdef HAVE_PCAP_CREATE
|
|
checkableColumns << IFTREE_COL_MONITOR_MODE;
|
|
#endif
|
|
|
|
editableColumns << IFTREE_COL_INTERFACE_COMMENT << IFTREE_COL_SNAPLEN << IFTREE_COL_PIPE_PATH;
|
|
|
|
#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
|
|
editableColumns << IFTREE_COL_BUFFERLEN;
|
|
#endif
|
|
}
|
|
|
|
InterfaceTreeCacheModel::~InterfaceTreeCacheModel()
|
|
{
|
|
#ifdef HAVE_LIBPCAP
|
|
/* This list should only exist, if the dialog is closed, without calling save first */
|
|
if ( newDevices.size() > 0 )
|
|
{
|
|
qDeleteAll(newDevices);
|
|
newDevices.clear();
|
|
}
|
|
#endif
|
|
|
|
delete storage;
|
|
delete sourceModel;
|
|
}
|
|
|
|
QVariant InterfaceTreeCacheModel::getColumnContent(int idx, int col, int role)
|
|
{
|
|
return InterfaceTreeCacheModel::data(index(idx, col), role);
|
|
}
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
void InterfaceTreeCacheModel::reset(int row)
|
|
{
|
|
if ( row < 0 )
|
|
{
|
|
delete storage;
|
|
storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>();
|
|
}
|
|
else
|
|
{
|
|
if ( storage->count() > row )
|
|
storage->remove(storage->keys().at(row));
|
|
}
|
|
}
|
|
|
|
void InterfaceTreeCacheModel::saveNewDevices()
|
|
{
|
|
QList<interface_t *>::const_iterator it = newDevices.constBegin();
|
|
/* idx is used for iterating only over the indices of the new devices. As all new
|
|
* devices are stored with an index higher then sourceModel->rowCount(), we start
|
|
* only with those storage indices.
|
|
* it is just the iterator over the new devices. A new device must not necessarily
|
|
* have storage, which will lead to that device not being stored in global_capture_opts */
|
|
for (int idx = sourceModel->rowCount(); it != newDevices.constEnd(); ++it, idx++)
|
|
{
|
|
interface_t * device = (interface_t *)(*it);
|
|
bool useDevice = false;
|
|
|
|
QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0);
|
|
/* When devices are being added, they are added using generic values. So only devices
|
|
* whose data have been changed should be used from here on out. */
|
|
if ( dataField != 0 )
|
|
{
|
|
if ( device->if_info.type != IF_PIPE )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( device->if_info.type == IF_PIPE )
|
|
{
|
|
QVariant saveValue = dataField->value(IFTREE_COL_PIPE_PATH);
|
|
if ( saveValue.isValid() )
|
|
{
|
|
g_free(device->if_info.name);
|
|
device->if_info.name = qstring_strdup(saveValue.toString());
|
|
|
|
g_free(device->name);
|
|
device->name = qstring_strdup(saveValue.toString());
|
|
|
|
g_free(device->display_name);
|
|
device->display_name = qstring_strdup(saveValue.toString());
|
|
useDevice = true;
|
|
}
|
|
}
|
|
|
|
if ( useDevice )
|
|
g_array_append_val(global_capture_opts.all_ifaces, *device);
|
|
|
|
}
|
|
|
|
/* All entries of this new devices have been considered */
|
|
storage->remove(idx);
|
|
delete dataField;
|
|
}
|
|
|
|
qDeleteAll(newDevices);
|
|
newDevices.clear();
|
|
}
|
|
|
|
void InterfaceTreeCacheModel::save()
|
|
{
|
|
if ( storage->count() == 0 )
|
|
return;
|
|
|
|
QMap<char**, QStringList> prefStorage;
|
|
|
|
|
|
/* Storing new devices first including their changed values */
|
|
saveNewDevices();
|
|
|
|
|
|
for(unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++)
|
|
{
|
|
interface_t device = g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
|
|
|
|
if (! device.name )
|
|
continue;
|
|
|
|
/* Try to load a saved value row for this index */
|
|
QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0);
|
|
|
|
/* Handle the storing of values for this device here */
|
|
if ( dataField )
|
|
{
|
|
QMap<InterfaceTreeColumns, QVariant>::const_iterator it = dataField->constBegin();
|
|
while ( it != dataField->constEnd() )
|
|
{
|
|
InterfaceTreeColumns col = it.key();
|
|
QVariant saveValue = it.value();
|
|
|
|
/* Setting the field values for each individual saved value cannot be generic, as the
|
|
* struct cannot be accessed in a generic way. Therefore below, each individually changed
|
|
* value has to be handled separately. Comments are stored only in the preference file
|
|
* and applied to the data name during loading. Therefore comments are not handled here */
|
|
|
|
if ( col == IFTREE_COL_HIDDEN )
|
|
{
|
|
device.hidden = saveValue.toBool();
|
|
}
|
|
#ifdef HAVE_EXTCAP
|
|
else if ( device.if_info.type == IF_EXTCAP )
|
|
{
|
|
/* extcap interfaces do not have the following columns.
|
|
* ATTENTION: all generic columns must be added, BEFORE this
|
|
* if-clause, or they will be ignored for extcap interfaces */
|
|
}
|
|
#endif
|
|
else if ( col == IFTREE_COL_PROMISCUOUSMODE )
|
|
{
|
|
device.pmode = saveValue.toBool();
|
|
}
|
|
#ifdef HAVE_PCAP_CREATE
|
|
else if ( col == IFTREE_COL_MONITOR_MODE )
|
|
{
|
|
device.monitor_mode_enabled = saveValue.toBool();
|
|
}
|
|
#endif
|
|
else if ( col == IFTREE_COL_SNAPLEN )
|
|
{
|
|
int iVal = saveValue.toInt();
|
|
if ( iVal != WTAP_MAX_PACKET_SIZE )
|
|
{
|
|
device.has_snaplen = true;
|
|
device.snaplen = iVal;
|
|
}
|
|
else
|
|
{
|
|
device.has_snaplen = false;
|
|
device.snaplen = WTAP_MAX_PACKET_SIZE;
|
|
}
|
|
}
|
|
#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
|
|
else if ( col == IFTREE_COL_BUFFERLEN )
|
|
{
|
|
device.buffer = saveValue.toInt();
|
|
}
|
|
#endif
|
|
|
|
global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, idx);
|
|
g_array_insert_val(global_capture_opts.all_ifaces, idx, device);
|
|
|
|
++it;
|
|
}
|
|
}
|
|
|
|
QVariant content = getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::CheckStateRole);
|
|
if ( content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Unchecked )
|
|
prefStorage[&prefs.capture_devices_hide] << QString(device.name);
|
|
|
|
content = getColumnContent(idx, IFTREE_COL_INTERFACE_COMMENT);
|
|
if ( content.isValid() && content.toString().size() > 0 )
|
|
prefStorage[&prefs.capture_devices_descr] << QString("%1(%2)").arg(device.name).arg(content.toString());
|
|
|
|
bool allowExtendedColumns = true;
|
|
#ifdef HAVE_EXTCAP
|
|
if ( device.if_info.type == IF_EXTCAP )
|
|
allowExtendedColumns = false;
|
|
#endif
|
|
if ( allowExtendedColumns )
|
|
{
|
|
content = getColumnContent(idx, IFTREE_COL_PROMISCUOUSMODE, Qt::CheckStateRole);
|
|
if ( content.isValid() )
|
|
{
|
|
bool value = static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked;
|
|
prefStorage[&prefs.capture_devices_pmode] << QString("%1(%2)").arg(device.name).arg(value ? 1 : 0);
|
|
}
|
|
|
|
#ifdef HAVE_PCAP_CREATE
|
|
content = getColumnContent(idx, IFTREE_COL_MONITOR_MODE, Qt::CheckStateRole);
|
|
if ( content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked )
|
|
prefStorage[&prefs.capture_devices_monitor_mode] << QString(device.name);
|
|
#endif
|
|
|
|
content = getColumnContent(idx, IFTREE_COL_SNAPLEN);
|
|
if ( content.isValid() )
|
|
{
|
|
int value = content.toInt();
|
|
prefStorage[&prefs.capture_devices_snaplen] <<
|
|
QString("%1:%2(%3)").arg(device.name).
|
|
arg(device.has_snaplen ? 1 : 0).
|
|
arg(device.has_snaplen ? value : WTAP_MAX_PACKET_SIZE);
|
|
}
|
|
|
|
#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
|
|
content = getColumnContent(idx, IFTREE_COL_BUFFERLEN);
|
|
if ( content.isValid() )
|
|
{
|
|
int value = content.toInt();
|
|
if ( value != -1 )
|
|
{
|
|
prefStorage[&prefs.capture_devices_buffersize] <<
|
|
QString("%1(%2)").arg(device.name).
|
|
arg(value);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
QMap<char**, QStringList>::const_iterator it = prefStorage.constBegin();
|
|
while ( it != prefStorage.constEnd() )
|
|
{
|
|
char ** key = it.key();
|
|
|
|
g_free(*key);
|
|
*key = qstring_strdup(it.value().join(","));
|
|
|
|
++it;
|
|
}
|
|
|
|
wsApp->emitAppSignal(WiresharkApplication::LocalInterfacesChanged);
|
|
}
|
|
#endif
|
|
|
|
int InterfaceTreeCacheModel::rowCount(const QModelIndex & parent) const
|
|
{
|
|
int totalCount = sourceModel->rowCount(parent);
|
|
#ifdef HAVE_LIBPCAP
|
|
totalCount += newDevices.size();
|
|
#endif
|
|
return totalCount;
|
|
}
|
|
|
|
bool InterfaceTreeCacheModel::changeIsAllowed(InterfaceTreeColumns col) const
|
|
{
|
|
if ( editableColumns.contains(col) || checkableColumns.contains(col) )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
interface_t * InterfaceTreeCacheModel::lookup(const QModelIndex &index) const
|
|
{
|
|
interface_t * result = 0;
|
|
|
|
if ( ! index.isValid() )
|
|
return result;
|
|
if ( ! global_capture_opts.all_ifaces && newDevices.size() == 0 )
|
|
return result;
|
|
|
|
int idx = index.row();
|
|
|
|
if ( (unsigned int) idx >= global_capture_opts.all_ifaces->len )
|
|
{
|
|
idx = idx - global_capture_opts.all_ifaces->len;
|
|
if ( idx < newDevices.size() )
|
|
result = newDevices[idx];
|
|
}
|
|
else
|
|
{
|
|
result = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
/* This checks if the column can be edited for the given index. This differs from
|
|
* isAvailableField in such a way, that it is only used in flags and not any
|
|
* other method.*/
|
|
bool InterfaceTreeCacheModel::isAllowedToBeEdited(const QModelIndex &index) const
|
|
{
|
|
Q_UNUSED(index)
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
interface_t * device = lookup(index);
|
|
if ( device == 0 )
|
|
return false;
|
|
|
|
#ifdef HAVE_EXTCAP
|
|
InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
|
|
if ( device->if_info.type == IF_EXTCAP )
|
|
{
|
|
/* extcap interfaces do not have those settings */
|
|
if ( col == IFTREE_COL_PROMISCUOUSMODE || col == IFTREE_COL_SNAPLEN )
|
|
return false;
|
|
#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
|
|
if ( col == IFTREE_COL_BUFFERLEN )
|
|
return false;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
// Whether this field is available for modification and display.
|
|
bool InterfaceTreeCacheModel::isAvailableField(const QModelIndex &index) const
|
|
{
|
|
Q_UNUSED(index)
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
interface_t * device = lookup(index);
|
|
|
|
if ( device == 0 )
|
|
return false;
|
|
|
|
InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
|
|
if ( col == IFTREE_COL_HIDDEN )
|
|
{
|
|
// Do not allow default capture interface to be hidden.
|
|
if ( ! g_strcmp0(prefs.capture_device, device->display_name) )
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
Qt::ItemFlags InterfaceTreeCacheModel::flags(const QModelIndex &index) const
|
|
{
|
|
if ( ! index.isValid() )
|
|
return 0;
|
|
|
|
Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
|
|
|
InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
|
|
|
|
if ( changeIsAllowed(col) && isAvailableField(index) && isAllowedToBeEdited(index) )
|
|
{
|
|
if ( checkableColumns.contains(col) )
|
|
{
|
|
flags |= Qt::ItemIsUserCheckable;
|
|
}
|
|
else
|
|
{
|
|
flags |= Qt::ItemIsEditable;
|
|
}
|
|
}
|
|
|
|
return flags;
|
|
}
|
|
|
|
bool InterfaceTreeCacheModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
{
|
|
if ( ! index.isValid() )
|
|
return false;
|
|
|
|
if ( ! isAvailableField(index) )
|
|
return false;
|
|
|
|
int row = index.row();
|
|
InterfaceTreeColumns col = (InterfaceTreeColumns)index.column();
|
|
|
|
if ( role == Qt::CheckStateRole || role == Qt::EditRole )
|
|
{
|
|
if ( changeIsAllowed( col ) )
|
|
{
|
|
QVariant saveValue = value;
|
|
|
|
QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
|
|
/* obtain the list of already stored changes for this row. If none exist
|
|
* create a new storage row for this entry */
|
|
if ( ( dataField = storage->value(row, 0) ) == 0 )
|
|
{
|
|
dataField = new QMap<InterfaceTreeColumns, QVariant>();
|
|
storage->insert(row, dataField);
|
|
}
|
|
|
|
dataField->insert(col, saveValue);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
QVariant InterfaceTreeCacheModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if ( ! index.isValid() )
|
|
return QVariant();
|
|
|
|
int row = index.row();
|
|
|
|
InterfaceTreeColumns col = (InterfaceTreeColumns)index.column();
|
|
|
|
if ( isAvailableField(index) && isAllowedToBeEdited(index) )
|
|
{
|
|
if ( ( ( role == Qt::DisplayRole || role == Qt::EditRole ) && editableColumns.contains(col) ) ||
|
|
( role == Qt::CheckStateRole && checkableColumns.contains(col) ) )
|
|
{
|
|
QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
|
|
if ( ( dataField = storage->value(row, 0) ) != 0 )
|
|
{
|
|
if ( dataField->contains(col) )
|
|
{
|
|
return dataField->value(col, QVariant());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( role == Qt::CheckStateRole )
|
|
return QVariant();
|
|
else if ( role == Qt::DisplayRole )
|
|
return QString(UTF8_EM_DASH);
|
|
}
|
|
|
|
if ( row < sourceModel->rowCount() )
|
|
{
|
|
return sourceModel->data(index, role);
|
|
}
|
|
#ifdef HAVE_LIBPCAP
|
|
else
|
|
{
|
|
/* Handle all fields, which will have to be displayed for new devices. Only pipes
|
|
* are supported at the moment, so the information to be displayed is pretty limited.
|
|
* After saving, the devices are stored in global_capture_opts and no longer
|
|
* classify as new devices. */
|
|
interface_t * device = lookup(index);
|
|
|
|
if ( device != 0 )
|
|
{
|
|
if ( role == Qt::DisplayRole || role == Qt::EditRole )
|
|
{
|
|
if ( col == IFTREE_COL_PIPE_PATH ||
|
|
col == IFTREE_COL_NAME ||
|
|
col == IFTREE_COL_INTERFACE_NAME )
|
|
{
|
|
|
|
QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
|
|
if ( ( dataField = storage->value(row, 0) ) != 0 &&
|
|
dataField->contains(IFTREE_COL_PIPE_PATH) )
|
|
{
|
|
return dataField->value(IFTREE_COL_PIPE_PATH, QVariant());
|
|
}
|
|
else
|
|
return QString(device->name);
|
|
}
|
|
else if ( col == IFTREE_COL_TYPE )
|
|
{
|
|
return QVariant::fromValue((int)device->if_info.type);
|
|
}
|
|
}
|
|
else if ( role == Qt::CheckStateRole )
|
|
{
|
|
if ( col == IFTREE_COL_HIDDEN )
|
|
{
|
|
// Do not allow default capture interface to be hidden.
|
|
if ( ! g_strcmp0(prefs.capture_device, device->display_name) )
|
|
return QVariant();
|
|
|
|
/* Hidden is a de-selection, therefore inverted logic here */
|
|
return device->hidden ? Qt::Unchecked : Qt::Checked;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return QVariant();
|
|
}
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
QModelIndex InterfaceTreeCacheModel::index(int row, int column, const QModelIndex &parent) const
|
|
{
|
|
if ( row >= sourceModel->rowCount() && ( row - sourceModel->rowCount() ) < newDevices.count() )
|
|
{
|
|
return createIndex(row, column, (void *)0);
|
|
}
|
|
|
|
return sourceModel->index(row, column, parent);
|
|
}
|
|
|
|
void InterfaceTreeCacheModel::addDevice(interface_t * newDevice)
|
|
{
|
|
emit beginInsertRows(QModelIndex(), rowCount(), rowCount());
|
|
newDevices << newDevice;
|
|
emit endInsertRows();
|
|
}
|
|
|
|
void InterfaceTreeCacheModel::deleteDevice(const QModelIndex &index)
|
|
{
|
|
if ( ! index.isValid() )
|
|
return;
|
|
|
|
emit beginRemoveRows(QModelIndex(), index.row(), index.row());
|
|
|
|
int row = index.row();
|
|
|
|
/* device is in newDevices */
|
|
if ( row >= sourceModel->rowCount() )
|
|
{
|
|
int newDeviceIdx = row - sourceModel->rowCount();
|
|
|
|
newDevices.removeAt(newDeviceIdx);
|
|
if ( storage->contains(index.row()) )
|
|
storage->remove(index.row());
|
|
|
|
/* The storage array has to be resorted, if the index, that was removed
|
|
* had been in the middle of the array. Can't start at index.row(), as
|
|
* it may not be contained in storage
|
|
* We must iterate using a list, not an iterator, otherwise the change
|
|
* will fold on itself. */
|
|
QList<int> storageKeys = storage->keys();
|
|
for ( int i = 0; i < storageKeys.size(); ++i )
|
|
{
|
|
int key = storageKeys.at(i);
|
|
if ( key > index.row() )
|
|
{
|
|
storage->insert(key - 1, storage->value(key));
|
|
storage->remove(key);
|
|
}
|
|
}
|
|
|
|
emit endRemoveRows();
|
|
}
|
|
else
|
|
{
|
|
g_array_remove_index(global_capture_opts.all_ifaces, row);
|
|
emit endRemoveRows();
|
|
wsApp->emitAppSignal(WiresharkApplication::LocalInterfacesChanged);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Editor modelines
|
|
*
|
|
* Local Variables:
|
|
* c-basic-offset: 4
|
|
* tab-width: 8
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*
|
|
* ex: set shiftwidth=4 tabstop=8 expandtab:
|
|
* :indentSize=4:tabSize=8:noTabs=true:
|
|
*/
|
|
|