forked from osmocom/wireshark
![Chuck Craft](/assets/img/avatar_default.png)
inputMask populates the field with a space for each character in the mask. Mouse people that click in the field may position the cursor at the far right of the mask and not be able to enter a value. https://www.qtcentre.org/threads/7106-QLineEdit-and-input-mask Remove the inputMask from the field definition and add a validator.
3160 lines
120 KiB
C++
3160 lines
120 KiB
C++
/* main_window.cpp
|
|
*
|
|
* Wireshark - Network traffic analyzer
|
|
* By Gerald Combs <gerald@wireshark.org>
|
|
* Copyright 1998 Gerald Combs
|
|
*
|
|
* SPDX-License-Identifier: GPL-2.0-or-later
|
|
*/
|
|
|
|
#include "main_window.h"
|
|
|
|
/*
|
|
* The generated Ui_MainWindow::setupUi() can grow larger than our configured limit,
|
|
* so turn off -Wframe-larger-than= for ui_main_window.h.
|
|
*/
|
|
DIAG_OFF(frame-larger-than=)
|
|
#include <ui_main_window.h>
|
|
DIAG_ON(frame-larger-than=)
|
|
|
|
#include <epan/addr_resolv.h>
|
|
#include "epan/conversation_filter.h"
|
|
#include <epan/epan_dissect.h>
|
|
#include <wsutil/filesystem.h>
|
|
#include <wsutil/wslog.h>
|
|
#include <wsutil/ws_assert.h>
|
|
#include <ui/version_info.h>
|
|
#include <epan/prefs.h>
|
|
#include <epan/stats_tree_priv.h>
|
|
#include <epan/plugin_if.h>
|
|
#include <epan/export_object.h>
|
|
#include <frame_tvbuff.h>
|
|
|
|
#include "ui/iface_toolbar.h"
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
#include "ui/capture.h"
|
|
#include <capture/capture_session.h>
|
|
#endif
|
|
|
|
#include "ui/alert_box.h"
|
|
#ifdef HAVE_LIBPCAP
|
|
#include "ui/capture_ui_utils.h"
|
|
#endif
|
|
#include "ui/capture_globals.h"
|
|
#include "ui/main_statusbar.h"
|
|
#include "ui/recent.h"
|
|
#include "ui/recent_utils.h"
|
|
#include "ui/util.h"
|
|
#include "ui/preference_utils.h"
|
|
|
|
#include "byte_view_tab.h"
|
|
#ifdef HAVE_LIBPCAP
|
|
#include "capture_options_dialog.h"
|
|
#endif
|
|
#include "conversation_colorize_action.h"
|
|
#include "export_dissection_dialog.h"
|
|
#include "export_object_action.h"
|
|
#include "file_set_dialog.h"
|
|
#include "filter_dialog.h"
|
|
#include "funnel_statistics.h"
|
|
#include "import_text_dialog.h"
|
|
#include "interface_toolbar.h"
|
|
#include "packet_diagram.h"
|
|
#include "packet_list.h"
|
|
#include "proto_tree.h"
|
|
#include "simple_dialog.h"
|
|
#include "tap_parameter_dialog.h"
|
|
#include "wireless_frame.h"
|
|
#include <ui/qt/widgets/wireless_timeline.h>
|
|
#include "wireshark_application.h"
|
|
|
|
#include <ui/qt/widgets/additional_toolbar.h>
|
|
#include <ui/qt/widgets/display_filter_edit.h>
|
|
#include <ui/qt/widgets/filter_expression_toolbar.h>
|
|
|
|
#include <ui/qt/utils/color_utils.h>
|
|
#include <ui/qt/utils/qt_ui_utils.h>
|
|
#include <ui/qt/utils/stock_icon.h>
|
|
#include <ui/qt/utils/variant_pointer.h>
|
|
|
|
#include <QAction>
|
|
#include <QActionGroup>
|
|
#include <QIntValidator>
|
|
#include <QKeyEvent>
|
|
#include <QList>
|
|
#include <QMessageBox>
|
|
#include <QMetaObject>
|
|
#include <QMimeData>
|
|
#include <QTabWidget>
|
|
#include <QTextCodec>
|
|
#include <QToolButton>
|
|
#include <QTreeWidget>
|
|
#include <QUrl>
|
|
|
|
//menu_recent_file_write_all
|
|
|
|
// If we ever add support for multiple windows this will need to be replaced.
|
|
static MainWindow *gbl_cur_main_window_ = NULL;
|
|
|
|
void pipe_input_set_handler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
|
|
{
|
|
gbl_cur_main_window_->setPipeInputHandler(source, user_data, child_process, input_cb);
|
|
}
|
|
|
|
static void plugin_if_mainwindow_apply_filter(GHashTable * data_set)
|
|
{
|
|
if (!gbl_cur_main_window_ || !data_set)
|
|
return;
|
|
|
|
if (g_hash_table_lookup_extended(data_set, "filter_string", NULL, NULL)) {
|
|
QString filter((const char *)g_hash_table_lookup(data_set, "filter_string"));
|
|
gbl_cur_main_window_->filterPackets(filter);
|
|
}
|
|
}
|
|
|
|
static void plugin_if_mainwindow_preference(GHashTable * data_set)
|
|
{
|
|
if (!gbl_cur_main_window_ || !data_set)
|
|
return;
|
|
|
|
const char * module_name;
|
|
const char * pref_name;
|
|
const char * pref_value;
|
|
|
|
DIAG_OFF_CAST_AWAY_CONST
|
|
if (g_hash_table_lookup_extended(data_set, "pref_module", NULL, (gpointer *)&module_name) &&
|
|
g_hash_table_lookup_extended(data_set, "pref_key", NULL, (gpointer *)&pref_name) &&
|
|
g_hash_table_lookup_extended(data_set, "pref_value", NULL, (gpointer *)&pref_value))
|
|
{
|
|
unsigned int changed_flags = prefs_store_ext(module_name, pref_name, pref_value);
|
|
if (changed_flags) {
|
|
wsApp->emitAppSignal(WiresharkApplication::PacketDissectionChanged);
|
|
wsApp->emitAppSignal(WiresharkApplication::PreferencesChanged);
|
|
}
|
|
}
|
|
DIAG_ON_CAST_AWAY_CONST
|
|
}
|
|
|
|
static void plugin_if_mainwindow_gotoframe(GHashTable * data_set)
|
|
{
|
|
if (!gbl_cur_main_window_ || !data_set)
|
|
return;
|
|
|
|
gpointer framenr;
|
|
|
|
if (g_hash_table_lookup_extended(data_set, "frame_nr", NULL, &framenr)) {
|
|
if (GPOINTER_TO_UINT(framenr) != 0)
|
|
gbl_cur_main_window_->gotoFrame(GPOINTER_TO_UINT(framenr));
|
|
}
|
|
}
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
|
|
static void plugin_if_mainwindow_get_ws_info(GHashTable * data_set)
|
|
{
|
|
if (!gbl_cur_main_window_ || !data_set)
|
|
return;
|
|
|
|
ws_info_t *ws_info = NULL;
|
|
|
|
if (!g_hash_table_lookup_extended(data_set, "ws_info", NULL, (void**)&ws_info))
|
|
return;
|
|
|
|
CaptureFile *cfWrap = gbl_cur_main_window_->captureFile();
|
|
capture_file *cf = cfWrap->capFile();
|
|
|
|
ws_info->ws_info_supported = true;
|
|
|
|
/* If we have a filename attached to ws_info clear it */
|
|
if (ws_info->cf_filename != NULL)
|
|
{
|
|
g_free(ws_info->cf_filename);
|
|
ws_info->cf_filename = NULL;
|
|
}
|
|
|
|
/* Determine the true state of the capture file. We return the true state in
|
|
the ws_info structure and DON'T CHANGE the cf->state as we don't want to cause problems
|
|
with code that follows this. */
|
|
if (cf)
|
|
{
|
|
if (cf->filename)
|
|
{
|
|
/* As we have a cf->filename we'll use the name and the state */
|
|
ws_info->cf_filename = g_strdup(cf->filename);
|
|
ws_info->cf_state = cf->state;
|
|
}
|
|
else
|
|
{
|
|
/* When we come through here the cf->state can show FILE_READ_DONE even though the
|
|
file is actually closed (no filename). A better fix would be to have a
|
|
FILE_CLOSE_PENDING state but that involves a lot of code change elsewhere. */
|
|
ws_info->cf_state = FILE_CLOSED;
|
|
}
|
|
}
|
|
|
|
if (!ws_info->cf_filename)
|
|
{
|
|
/* We may have a filename associated with the main window so let's use it */
|
|
QString fileNameString = gbl_cur_main_window_->getMwFileName();
|
|
if (fileNameString.length())
|
|
{
|
|
QByteArray ba = fileNameString.toLatin1();
|
|
const char *c_file_name = ba.data();
|
|
ws_info->cf_filename = g_strdup(c_file_name);
|
|
}
|
|
}
|
|
|
|
if (cf) {
|
|
ws_info->cf_count = cf->count;
|
|
|
|
QList<int> rows = gbl_cur_main_window_->selectedRows();
|
|
frame_data * fdata = NULL;
|
|
if (rows.count() > 0)
|
|
fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0));
|
|
|
|
if (cf->state == FILE_READ_DONE && fdata) {
|
|
ws_info->cf_framenr = fdata->num;
|
|
ws_info->frame_passed_dfilter = (fdata->passed_dfilter == 1);
|
|
}
|
|
else {
|
|
ws_info->cf_framenr = 0;
|
|
ws_info->frame_passed_dfilter = FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Initialise the other ws_info structure values */
|
|
ws_info->cf_count = 0;
|
|
ws_info->cf_framenr = 0;
|
|
ws_info->frame_passed_dfilter = FALSE;
|
|
}
|
|
}
|
|
|
|
#endif /* HAVE_LIBPCAP */
|
|
|
|
static void plugin_if_mainwindow_get_frame_data(GHashTable* data_set)
|
|
{
|
|
if (!gbl_cur_main_window_ || !data_set)
|
|
return;
|
|
|
|
plugin_if_frame_data_cb extract_cb;
|
|
void* user_data;
|
|
void** ret_value_ptr;
|
|
|
|
if (g_hash_table_lookup_extended(data_set, "extract_cb", NULL, (void**)&extract_cb) &&
|
|
g_hash_table_lookup_extended(data_set, "user_data", NULL, (void**)&user_data) &&
|
|
g_hash_table_lookup_extended(data_set, "ret_value_ptr", NULL, (void**)&ret_value_ptr))
|
|
{
|
|
QList<int> rows = gbl_cur_main_window_->selectedRows();
|
|
if (rows.count() > 0) {
|
|
frame_data* fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0));
|
|
if (fdata) {
|
|
*ret_value_ptr = extract_cb(fdata, user_data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void plugin_if_mainwindow_get_capture_file(GHashTable* data_set)
|
|
{
|
|
if (!gbl_cur_main_window_ || !data_set)
|
|
return;
|
|
|
|
plugin_if_capture_file_cb extract_cb;
|
|
void* user_data;
|
|
void** ret_value_ptr;
|
|
|
|
if (g_hash_table_lookup_extended(data_set, "extract_cb", NULL, (void**)&extract_cb) &&
|
|
g_hash_table_lookup_extended(data_set, "user_data", NULL, (void**)&user_data) &&
|
|
g_hash_table_lookup_extended(data_set, "ret_value_ptr", NULL, (void**)&ret_value_ptr))
|
|
{
|
|
CaptureFile* cfWrap = gbl_cur_main_window_->captureFile();
|
|
capture_file* cf = cfWrap->capFile();
|
|
if (cf) {
|
|
*ret_value_ptr = extract_cb(cf, user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void plugin_if_mainwindow_update_toolbars(GHashTable * data_set)
|
|
{
|
|
if (!gbl_cur_main_window_ || !data_set)
|
|
return;
|
|
|
|
if (g_hash_table_lookup_extended(data_set, "toolbar_name", NULL, NULL)) {
|
|
QString toolbarName((const char *)g_hash_table_lookup(data_set, "toolbar_name"));
|
|
gbl_cur_main_window_->removeAdditionalToolbar(toolbarName);
|
|
|
|
}
|
|
}
|
|
|
|
static void mainwindow_add_toolbar(const iface_toolbar *toolbar_entry)
|
|
{
|
|
if (gbl_cur_main_window_ && toolbar_entry)
|
|
{
|
|
gbl_cur_main_window_->addInterfaceToolbar(toolbar_entry);
|
|
}
|
|
}
|
|
|
|
static void mainwindow_remove_toolbar(const gchar *menu_title)
|
|
{
|
|
if (gbl_cur_main_window_ && menu_title)
|
|
{
|
|
gbl_cur_main_window_->removeInterfaceToolbar(menu_title);
|
|
}
|
|
}
|
|
|
|
QMenu* MainWindow::findOrAddMenu(QMenu *parent_menu, QString& menu_text) {
|
|
QList<QAction *> actions = parent_menu->actions();
|
|
QList<QAction *>::const_iterator i;
|
|
for (i = actions.constBegin(); i != actions.constEnd(); ++i) {
|
|
if ((*i)->text()==menu_text) {
|
|
return (*i)->menu();
|
|
}
|
|
}
|
|
// If we get here there menu entry was not found, add a sub menu
|
|
return parent_menu->addMenu(menu_text);
|
|
}
|
|
|
|
MainWindow::MainWindow(QWidget *parent) :
|
|
QMainWindow(parent),
|
|
main_ui_(new Ui::MainWindow),
|
|
cur_layout_(QVector<unsigned>()),
|
|
packet_list_(NULL),
|
|
proto_tree_(NULL),
|
|
previous_focus_(NULL),
|
|
file_set_dialog_(NULL),
|
|
show_hide_actions_(NULL),
|
|
time_display_actions_(NULL),
|
|
time_precision_actions_(NULL),
|
|
funnel_statistics_(NULL),
|
|
freeze_focus_(NULL),
|
|
was_maximized_(false),
|
|
capture_stopping_(false),
|
|
capture_filter_valid_(false)
|
|
#ifdef HAVE_LIBPCAP
|
|
, capture_options_dialog_(NULL)
|
|
, info_data_()
|
|
#endif
|
|
, display_filter_dlg_(NULL)
|
|
, capture_filter_dlg_(NULL)
|
|
#ifdef _WIN32
|
|
, pipe_timer_(NULL)
|
|
#else
|
|
, pipe_notifier_(NULL)
|
|
#endif
|
|
#if defined(Q_OS_MAC)
|
|
, dock_menu_(NULL)
|
|
#endif
|
|
{
|
|
if (!gbl_cur_main_window_) {
|
|
connect(wsApp, SIGNAL(openStatCommandDialog(QString, const char*, void*)),
|
|
this, SLOT(openStatCommandDialog(QString, const char*, void*)));
|
|
connect(wsApp, SIGNAL(openTapParameterDialog(QString, const QString, void*)),
|
|
this, SLOT(openTapParameterDialog(QString, const QString, void*)));
|
|
}
|
|
gbl_cur_main_window_ = this;
|
|
#ifdef HAVE_LIBPCAP
|
|
capture_input_init(&cap_session_, CaptureFile::globalCapFile());
|
|
#endif
|
|
|
|
findTextCodecs();
|
|
// setpUi calls QMetaObject::connectSlotsByName(this). connectSlotsByName
|
|
// iterates over *all* of our children, looking for matching "on_" slots.
|
|
// The fewer children we have at this point the better.
|
|
main_ui_->setupUi(this);
|
|
#ifdef HAVE_SOFTWARE_UPDATE
|
|
update_action_ = new QAction(tr("Check for Updates…"), main_ui_->menuHelp);
|
|
#endif
|
|
#if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
|
|
wireless_frame_ = new WirelessFrame(this);
|
|
main_ui_->wirelessToolBar->addWidget(wireless_frame_);
|
|
#else
|
|
removeToolBar(main_ui_->wirelessToolBar);
|
|
main_ui_->menuView->removeAction(main_ui_->actionViewWirelessToolbar);
|
|
#endif
|
|
|
|
setWindowIcon(wsApp->normalIcon());
|
|
setTitlebarForCaptureFile();
|
|
setMenusForCaptureFile();
|
|
setForCapturedPackets(false);
|
|
setMenusForFileSet(false);
|
|
interfaceSelectionChanged();
|
|
loadWindowGeometry();
|
|
|
|
#ifndef HAVE_LUA
|
|
main_ui_->actionAnalyzeReloadLuaPlugins->setVisible(false);
|
|
#endif
|
|
|
|
qRegisterMetaType<FilterAction::Action>("FilterAction::Action");
|
|
qRegisterMetaType<FilterAction::ActionType>("FilterAction::ActionType");
|
|
connect(this, SIGNAL(filterAction(QString, FilterAction::Action, FilterAction::ActionType)),
|
|
this, SLOT(queuedFilterAction(QString, FilterAction::Action, FilterAction::ActionType)),
|
|
Qt::QueuedConnection);
|
|
|
|
//To prevent users use features before initialization complete
|
|
//Otherwise unexpected problems may occur
|
|
setFeaturesEnabled(false);
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(setFeaturesEnabled()));
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(applyGlobalCommandLineOptions()));
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(zoomText()));
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(initViewColorizeMenu()));
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addStatsPluginsToMenu()));
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addDynamicMenus()));
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addPluginIFStructures()));
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(initConversationMenus()));
|
|
connect(wsApp, SIGNAL(appInitialized()), this, SLOT(initExportObjectsMenus()));
|
|
|
|
connect(wsApp, SIGNAL(profileChanging()), this, SLOT(saveWindowGeometry()));
|
|
connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutPanes()));
|
|
connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutToolbars()));
|
|
connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(updatePreferenceActions()));
|
|
connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(zoomText()));
|
|
connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(setTitlebarForCaptureFile()));
|
|
|
|
connect(wsApp, SIGNAL(updateRecentCaptureStatus(const QString &, qint64, bool)), this, SLOT(updateRecentCaptures()));
|
|
updateRecentCaptures();
|
|
|
|
#if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN)
|
|
connect(wsApp, SIGNAL(softwareUpdateRequested()), this, SLOT(softwareUpdateRequested()),
|
|
Qt::BlockingQueuedConnection);
|
|
connect(wsApp, SIGNAL(softwareUpdateClose()), this, SLOT(close()),
|
|
Qt::BlockingQueuedConnection);
|
|
#endif
|
|
|
|
df_combo_box_ = new DisplayFilterCombo(this);
|
|
|
|
funnel_statistics_ = new FunnelStatistics(this, capture_file_);
|
|
connect(funnel_statistics_, &FunnelStatistics::setDisplayFilter, this, &MainWindow::setDisplayFilter);
|
|
connect(funnel_statistics_, SIGNAL(openCaptureFile(QString, QString)),
|
|
this, SLOT(openCaptureFile(QString, QString)));
|
|
|
|
file_set_dialog_ = new FileSetDialog(this);
|
|
connect(file_set_dialog_, SIGNAL(fileSetOpenCaptureFile(QString)),
|
|
this, SLOT(openCaptureFile(QString)));
|
|
|
|
initMainToolbarIcons();
|
|
|
|
main_ui_->displayFilterToolBar->insertWidget(main_ui_->actionNewDisplayFilterExpression, df_combo_box_);
|
|
|
|
// Make sure filter expressions overflow into a menu instead of a
|
|
// larger toolbar. We do this by adding them to a child toolbar.
|
|
// https://bugreports.qt.io/browse/QTBUG-2472
|
|
FilterExpressionToolBar *filter_expression_toolbar_ = new FilterExpressionToolBar(this);
|
|
connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterPreferences, this, &MainWindow::onFilterPreferences);
|
|
connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterSelected, this, &MainWindow::onFilterSelected);
|
|
connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterEdit, this, &MainWindow::onFilterEdit);
|
|
|
|
main_ui_->displayFilterToolBar->addWidget(filter_expression_toolbar_);
|
|
|
|
#if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
|
|
connect(wireless_frame_, SIGNAL(showWirelessPreferences(QString)),
|
|
this, SLOT(showPreferencesDialog(QString)));
|
|
#endif
|
|
|
|
main_ui_->goToFrame->hide();
|
|
connect(main_ui_->goToFrame, SIGNAL(visibilityChanged(bool)),
|
|
main_ui_->actionGoGoToPacket, SLOT(setChecked(bool)));
|
|
|
|
// XXX For some reason the cursor is drawn funny with an input mask set
|
|
// https://bugreports.qt-project.org/browse/QTBUG-7174
|
|
|
|
main_ui_->searchFrame->hide();
|
|
connect(main_ui_->searchFrame, SIGNAL(visibilityChanged(bool)),
|
|
main_ui_->actionEditFindPacket, SLOT(setChecked(bool)));
|
|
|
|
main_ui_->addressEditorFrame->hide();
|
|
main_ui_->columnEditorFrame->hide();
|
|
main_ui_->preferenceEditorFrame->hide();
|
|
main_ui_->filterExpressionFrame->hide();
|
|
|
|
#ifndef HAVE_LIBPCAP
|
|
main_ui_->menuCapture->setEnabled(false);
|
|
#endif
|
|
|
|
// Set OS specific shortcuts for fullscreen mode
|
|
#if defined(Q_OS_MAC)
|
|
main_ui_->actionViewFullScreen->setShortcut(QKeySequence::FullScreen);
|
|
#else
|
|
main_ui_->actionViewFullScreen->setShortcut(QKeySequence(Qt::Key_F11));
|
|
#endif
|
|
|
|
#if defined(Q_OS_MAC)
|
|
|
|
main_ui_->goToPacketLabel->setAttribute(Qt::WA_MacSmallSize, true);
|
|
main_ui_->goToLineEdit->setAttribute(Qt::WA_MacSmallSize, true);
|
|
main_ui_->goToGo->setAttribute(Qt::WA_MacSmallSize, true);
|
|
main_ui_->goToCancel->setAttribute(Qt::WA_MacSmallSize, true);
|
|
|
|
main_ui_->actionEditPreferences->setMenuRole(QAction::PreferencesRole);
|
|
|
|
#endif // Q_OS_MAC
|
|
|
|
// A billion-1 is equivalent to the inputMask 900000000 previously used
|
|
// Avoid QValidator::Intermediate values by using a top value of all 9's
|
|
#define MAX_GOTO_LINE 999999999
|
|
|
|
QIntValidator *goToLineQiv = new QIntValidator(0,MAX_GOTO_LINE,this);
|
|
main_ui_->goToLineEdit->setValidator(goToLineQiv);
|
|
|
|
#ifdef HAVE_SOFTWARE_UPDATE
|
|
QAction *update_sep = main_ui_->menuHelp->insertSeparator(main_ui_->actionHelpAbout);
|
|
main_ui_->menuHelp->insertAction(update_sep, update_action_);
|
|
connect(update_action_, SIGNAL(triggered()), this, SLOT(checkForUpdates()));
|
|
#endif
|
|
master_split_.setObjectName("splitterMaster");
|
|
extra_split_.setObjectName("splitterExtra");
|
|
master_split_.setChildrenCollapsible(false);
|
|
extra_split_.setChildrenCollapsible(false);
|
|
main_ui_->mainStack->addWidget(&master_split_);
|
|
|
|
empty_pane_.setObjectName("emptyPane");
|
|
empty_pane_.setVisible(false);
|
|
|
|
packet_list_ = new PacketList(&master_split_);
|
|
main_ui_->wirelessTimelineWidget->setPacketList(packet_list_);
|
|
connect(packet_list_, SIGNAL(framesSelected(QList<int>)), this, SLOT(setMenusForSelectedPacket()));
|
|
connect(packet_list_, SIGNAL(framesSelected(QList<int>)), this, SIGNAL(framesSelected(QList<int>)));
|
|
|
|
connect(main_ui_->menuPacketComment, SIGNAL(aboutToShow()), this, SLOT(setEditCommentsMenu()));
|
|
|
|
proto_tree_ = new ProtoTree(&master_split_);
|
|
proto_tree_->installEventFilter(this);
|
|
|
|
packet_list_->setProtoTree(proto_tree_);
|
|
packet_list_->installEventFilter(this);
|
|
|
|
packet_diagram_ = new PacketDiagram(&master_split_);
|
|
|
|
welcome_page_ = main_ui_->welcomePage;
|
|
|
|
connect(proto_tree_, &ProtoTree::fieldSelected,
|
|
this, &MainWindow::fieldSelected);
|
|
connect(packet_list_, &PacketList::fieldSelected,
|
|
this, &MainWindow::fieldSelected);
|
|
connect(this, &MainWindow::fieldSelected,
|
|
this, &MainWindow::setMenusForSelectedTreeRow);
|
|
connect(this, &MainWindow::fieldSelected,
|
|
main_ui_->statusBar, &MainStatusBar::selectedFieldChanged);
|
|
|
|
connect(this, &MainWindow::fieldHighlight,
|
|
main_ui_->statusBar, &MainStatusBar::highlightedFieldChanged);
|
|
connect(wsApp, &WiresharkApplication::captureActive,
|
|
this, &MainWindow::captureActive);
|
|
|
|
byte_view_tab_ = new ByteViewTab(&master_split_);
|
|
|
|
// Packet list and proto tree must exist before these are called.
|
|
setMenusForSelectedPacket();
|
|
setMenusForSelectedTreeRow();
|
|
|
|
initShowHideMainWidgets();
|
|
initTimeDisplayFormatMenu();
|
|
initTimePrecisionFormatMenu();
|
|
initFreezeActions();
|
|
updatePreferenceActions();
|
|
updateRecentActions();
|
|
setForCaptureInProgress(false);
|
|
|
|
setTabOrder(df_combo_box_->lineEdit(), packet_list_);
|
|
setTabOrder(packet_list_, proto_tree_);
|
|
|
|
connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)),
|
|
this, SLOT(captureEventHandler(CaptureEvent)));
|
|
connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)),
|
|
wsApp, SLOT(captureEventHandler(CaptureEvent)));
|
|
connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)),
|
|
main_ui_->statusBar, SLOT(captureEventHandler(CaptureEvent)));
|
|
|
|
connect(wsApp, SIGNAL(columnsChanged()),
|
|
packet_list_, SLOT(columnsChanged()));
|
|
connect(wsApp, SIGNAL(preferencesChanged()),
|
|
packet_list_, SLOT(preferencesChanged()));
|
|
connect(wsApp, SIGNAL(recentPreferencesRead()),
|
|
this, SLOT(applyRecentPaneGeometry()));
|
|
connect(wsApp, SIGNAL(recentPreferencesRead()),
|
|
this, SLOT(updateRecentActions()));
|
|
connect(wsApp, SIGNAL(packetDissectionChanged()),
|
|
this, SLOT(redissectPackets()), Qt::QueuedConnection);
|
|
|
|
connect(wsApp, SIGNAL(checkDisplayFilter()),
|
|
this, SLOT(checkDisplayFilter()));
|
|
connect(wsApp, SIGNAL(fieldsChanged()),
|
|
this, SLOT(fieldsChanged()));
|
|
connect(wsApp, SIGNAL(reloadLuaPlugins()),
|
|
this, SLOT(reloadLuaPlugins()));
|
|
|
|
connect(main_ui_->mainStack, SIGNAL(currentChanged(int)),
|
|
this, SLOT(mainStackChanged(int)));
|
|
|
|
connect(welcome_page_, SIGNAL(startCapture()),
|
|
this, SLOT(startCapture()));
|
|
connect(welcome_page_, SIGNAL(recentFileActivated(QString)),
|
|
this, SLOT(openCaptureFile(QString)));
|
|
|
|
connect(main_ui_->addressEditorFrame, &AddressEditorFrame::redissectPackets,
|
|
this, &MainWindow::redissectPackets);
|
|
connect(main_ui_->addressEditorFrame, &AddressEditorFrame::showNameResolutionPreferences,
|
|
this, &MainWindow::showPreferencesDialog);
|
|
connect(main_ui_->preferenceEditorFrame, &PreferenceEditorFrame::showProtocolPreferences,
|
|
this, &MainWindow::showPreferencesDialog);
|
|
connect(main_ui_->filterExpressionFrame, &FilterExpressionFrame::showPreferencesDialog,
|
|
this, &MainWindow::showPreferencesDialog);
|
|
connect(main_ui_->filterExpressionFrame, &FilterExpressionFrame::filterExpressionsChanged,
|
|
filter_expression_toolbar_, &FilterExpressionToolBar::filterExpressionsChanged);
|
|
|
|
/* Connect change of capture file */
|
|
connect(this, &MainWindow::setCaptureFile,
|
|
main_ui_->searchFrame, &SearchFrame::setCaptureFile);
|
|
connect(this, &MainWindow::setCaptureFile,
|
|
main_ui_->statusBar, &MainStatusBar::setCaptureFile);
|
|
connect(this, &MainWindow::setCaptureFile,
|
|
packet_list_, &PacketList::setCaptureFile);
|
|
connect(this, &MainWindow::setCaptureFile,
|
|
proto_tree_, &ProtoTree::setCaptureFile);
|
|
|
|
connect(wsApp, SIGNAL(zoomMonospaceFont(QFont)),
|
|
packet_list_, SLOT(setMonospaceFont(QFont)));
|
|
connect(wsApp, SIGNAL(zoomMonospaceFont(QFont)),
|
|
proto_tree_, SLOT(setMonospaceFont(QFont)));
|
|
|
|
connect(main_ui_->actionGoNextPacket, SIGNAL(triggered()),
|
|
packet_list_, SLOT(goNextPacket()));
|
|
connect(main_ui_->actionGoPreviousPacket, SIGNAL(triggered()),
|
|
packet_list_, SLOT(goPreviousPacket()));
|
|
connect(main_ui_->actionGoFirstPacket, SIGNAL(triggered()),
|
|
packet_list_, SLOT(goFirstPacket()));
|
|
connect(main_ui_->actionGoLastPacket, SIGNAL(triggered()),
|
|
packet_list_, SLOT(goLastPacket()));
|
|
connect(main_ui_->actionGoNextHistoryPacket, SIGNAL(triggered()),
|
|
packet_list_, SLOT(goNextHistoryPacket()));
|
|
connect(main_ui_->actionGoPreviousHistoryPacket, SIGNAL(triggered()),
|
|
packet_list_, SLOT(goPreviousHistoryPacket()));
|
|
|
|
connect(main_ui_->actionViewExpandSubtrees, SIGNAL(triggered()),
|
|
proto_tree_, SLOT(expandSubtrees()));
|
|
connect(main_ui_->actionViewCollapseSubtrees, SIGNAL(triggered()),
|
|
proto_tree_, SLOT(collapseSubtrees()));
|
|
connect(main_ui_->actionViewExpandAll, SIGNAL(triggered()),
|
|
proto_tree_, SLOT(expandAll()));
|
|
connect(main_ui_->actionViewCollapseAll, SIGNAL(triggered()),
|
|
proto_tree_, SLOT(collapseAll()));
|
|
|
|
connect(packet_list_, SIGNAL(packetDissectionChanged()),
|
|
this, SLOT(redissectPackets()));
|
|
connect(packet_list_, SIGNAL(showColumnPreferences(QString)),
|
|
this, SLOT(showPreferencesDialog(QString)));
|
|
connect(packet_list_, SIGNAL(showProtocolPreferences(QString)),
|
|
this, SLOT(showPreferencesDialog(QString)));
|
|
connect(packet_list_, SIGNAL(editProtocolPreference(preference*, pref_module*)),
|
|
main_ui_->preferenceEditorFrame, SLOT(editPreference(preference*, pref_module*)));
|
|
connect(packet_list_, SIGNAL(editColumn(int)), this, SLOT(showColumnEditor(int)));
|
|
connect(main_ui_->columnEditorFrame, SIGNAL(columnEdited()),
|
|
packet_list_, SLOT(columnsChanged()));
|
|
connect(packet_list_, SIGNAL(doubleClicked(QModelIndex)),
|
|
this, SLOT(openPacketDialog()));
|
|
connect(packet_list_, SIGNAL(packetListScrolled(bool)),
|
|
main_ui_->actionGoAutoScroll, SLOT(setChecked(bool)));
|
|
|
|
connect(proto_tree_, SIGNAL(openPacketInNewWindow(bool)),
|
|
this, SLOT(openPacketDialog(bool)));
|
|
connect(proto_tree_, SIGNAL(showProtocolPreferences(QString)),
|
|
this, SLOT(showPreferencesDialog(QString)));
|
|
connect(proto_tree_, SIGNAL(editProtocolPreference(preference*, pref_module*)),
|
|
main_ui_->preferenceEditorFrame, SLOT(editPreference(preference*, pref_module*)));
|
|
|
|
connect(main_ui_->statusBar, SIGNAL(showExpertInfo()),
|
|
this, SLOT(on_actionAnalyzeExpertInfo_triggered()));
|
|
|
|
connect(main_ui_->statusBar, SIGNAL(stopLoading()),
|
|
&capture_file_, SLOT(stopLoading()));
|
|
|
|
connect(main_ui_->statusBar, SIGNAL(editCaptureComment()),
|
|
this, SLOT(on_actionStatisticsCaptureFileProperties_triggered()));
|
|
|
|
connect(main_ui_->menuApplyAsFilter, &QMenu::aboutToShow,
|
|
this, &MainWindow::filterMenuAboutToShow);
|
|
connect(main_ui_->menuPrepareAFilter, &QMenu::aboutToShow,
|
|
this, &MainWindow::filterMenuAboutToShow);
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
QTreeWidget *iface_tree = findChild<QTreeWidget *>("interfaceTree");
|
|
if (iface_tree) {
|
|
connect(iface_tree, SIGNAL(itemSelectionChanged()),
|
|
this, SLOT(interfaceSelectionChanged()));
|
|
}
|
|
connect(main_ui_->welcomePage, SIGNAL(captureFilterSyntaxChanged(bool)),
|
|
this, SLOT(captureFilterSyntaxChanged(bool)));
|
|
|
|
connect(this, SIGNAL(showExtcapOptions(QString&, bool)),
|
|
this, SLOT(showExtcapOptionsDialog(QString&, bool)));
|
|
connect(this->welcome_page_, SIGNAL(showExtcapOptions(QString&, bool)),
|
|
this, SLOT(showExtcapOptionsDialog(QString&, bool)));
|
|
|
|
#endif // HAVE_LIBPCAP
|
|
|
|
/* Create plugin_if hooks */
|
|
plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_APPLY, plugin_if_mainwindow_apply_filter);
|
|
plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_PREPARE, plugin_if_mainwindow_apply_filter);
|
|
plugin_if_register_gui_cb(PLUGIN_IF_PREFERENCE_SAVE, plugin_if_mainwindow_preference);
|
|
plugin_if_register_gui_cb(PLUGIN_IF_GOTO_FRAME, plugin_if_mainwindow_gotoframe);
|
|
#ifdef HAVE_LIBPCAP
|
|
plugin_if_register_gui_cb(PLUGIN_IF_GET_WS_INFO, plugin_if_mainwindow_get_ws_info);
|
|
#endif
|
|
plugin_if_register_gui_cb(PLUGIN_IF_GET_FRAME_DATA, plugin_if_mainwindow_get_frame_data);
|
|
plugin_if_register_gui_cb(PLUGIN_IF_GET_CAPTURE_FILE, plugin_if_mainwindow_get_capture_file);
|
|
plugin_if_register_gui_cb(PLUGIN_IF_REMOVE_TOOLBAR, plugin_if_mainwindow_update_toolbars);
|
|
|
|
/* Register Interface Toolbar callbacks */
|
|
iface_toolbar_register_cb(mainwindow_add_toolbar, mainwindow_remove_toolbar);
|
|
|
|
showWelcome();
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
disconnect(main_ui_->mainStack, 0, 0, 0);
|
|
|
|
#ifndef Q_OS_MAC
|
|
// Below dialogs inherit GeometryStateDialog
|
|
// For reasons described in geometry_state_dialog.h no parent is set when
|
|
// instantiating the dialogs and as a resul objects are not automatically
|
|
// freed by its parent. Free then here explicitly to avoid leak and numerous
|
|
// Valgrind complaints.
|
|
delete file_set_dialog_;
|
|
delete capture_filter_dlg_;
|
|
delete display_filter_dlg_;
|
|
#ifdef HAVE_LIBPCAP
|
|
delete capture_options_dialog_;
|
|
#endif
|
|
|
|
#endif
|
|
delete main_ui_;
|
|
}
|
|
|
|
QString MainWindow::getFilter()
|
|
{
|
|
return df_combo_box_->currentText();
|
|
}
|
|
|
|
QMenu *MainWindow::createPopupMenu()
|
|
{
|
|
QMenu *menu = new QMenu();
|
|
menu->addAction(main_ui_->actionViewMainToolbar);
|
|
menu->addAction(main_ui_->actionViewFilterToolbar);
|
|
#if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
|
|
menu->addAction(main_ui_->actionViewWirelessToolbar);
|
|
#endif
|
|
|
|
if (!main_ui_->menuInterfaceToolbars->actions().isEmpty()) {
|
|
QMenu *submenu = menu->addMenu(main_ui_->menuInterfaceToolbars->title());
|
|
foreach(QAction *action, main_ui_->menuInterfaceToolbars->actions()) {
|
|
submenu->addAction(action);
|
|
}
|
|
}
|
|
|
|
if (!main_ui_->menuAdditionalToolbars->actions().isEmpty()) {
|
|
QMenu *subMenu = menu->addMenu(main_ui_->menuAdditionalToolbars->title());
|
|
foreach(QAction *action, main_ui_->menuAdditionalToolbars->actions()) {
|
|
subMenu->addAction(action);
|
|
}
|
|
}
|
|
|
|
menu->addAction(main_ui_->actionViewStatusBar);
|
|
|
|
menu->addSeparator();
|
|
menu->addAction(main_ui_->actionViewPacketList);
|
|
menu->addAction(main_ui_->actionViewPacketDetails);
|
|
menu->addAction(main_ui_->actionViewPacketBytes);
|
|
menu->addAction(main_ui_->actionViewPacketDiagram);
|
|
return menu;
|
|
}
|
|
|
|
void MainWindow::addInterfaceToolbar(const iface_toolbar *toolbar_entry)
|
|
{
|
|
QMenu *menu = main_ui_->menuInterfaceToolbars;
|
|
bool visible = g_list_find_custom(recent.interface_toolbars, toolbar_entry->menu_title, (GCompareFunc)strcmp) ? true : false;
|
|
|
|
QString title = QString().fromUtf8(toolbar_entry->menu_title);
|
|
QAction *action = new QAction(title, menu);
|
|
action->setEnabled(true);
|
|
action->setCheckable(true);
|
|
action->setChecked(visible);
|
|
action->setToolTip(tr("Show or hide the toolbar"));
|
|
|
|
QAction *before = NULL;
|
|
foreach(QAction *action, menu->actions()) {
|
|
// Ensure we add the menu entries in sorted order
|
|
if (action->text().compare(title, Qt::CaseInsensitive) > 0) {
|
|
before = action;
|
|
break;
|
|
}
|
|
}
|
|
menu->insertAction(before, action);
|
|
|
|
InterfaceToolbar *interface_toolbar = new InterfaceToolbar(this, toolbar_entry);
|
|
connect(wsApp, SIGNAL(appInitialized()), interface_toolbar, SLOT(interfaceListChanged()));
|
|
connect(wsApp, SIGNAL(localInterfaceListChanged()), interface_toolbar, SLOT(interfaceListChanged()));
|
|
|
|
QToolBar *toolbar = new QToolBar(this);
|
|
toolbar->addWidget(interface_toolbar);
|
|
toolbar->setMovable(false);
|
|
toolbar->setVisible(visible);
|
|
|
|
action->setData(QVariant::fromValue(toolbar));
|
|
|
|
addToolBar(Qt::TopToolBarArea, toolbar);
|
|
insertToolBarBreak(toolbar);
|
|
|
|
if (show_hide_actions_) {
|
|
show_hide_actions_->addAction(action);
|
|
}
|
|
|
|
menu->menuAction()->setVisible(true);
|
|
}
|
|
|
|
void MainWindow::removeInterfaceToolbar(const gchar *menu_title)
|
|
{
|
|
QMenu *menu = main_ui_->menuInterfaceToolbars;
|
|
QAction *action = NULL;
|
|
QMap<QAction *, QWidget *>::iterator i;
|
|
|
|
QString title = QString().fromUtf8(menu_title);
|
|
foreach(action, menu->actions()) {
|
|
if (title.compare(action->text()) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (action) {
|
|
if (show_hide_actions_) {
|
|
show_hide_actions_->removeAction(action);
|
|
}
|
|
menu->removeAction(action);
|
|
|
|
QToolBar *toolbar = action->data().value<QToolBar *>();
|
|
removeToolBar(toolbar);
|
|
|
|
delete action;
|
|
delete toolbar;
|
|
}
|
|
|
|
menu->menuAction()->setVisible(!menu->actions().isEmpty());
|
|
}
|
|
|
|
void MainWindow::setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
|
|
{
|
|
pipe_source_ = source;
|
|
pipe_child_process_ = child_process;
|
|
pipe_user_data_ = user_data;
|
|
pipe_input_cb_ = input_cb;
|
|
|
|
#ifdef _WIN32
|
|
/* Tricky to use pipes in win9x, as no concept of wait. NT can
|
|
do this but that doesn't cover all win32 platforms. GTK can do
|
|
this but doesn't seem to work over processes. Attempt to do
|
|
something similar here, start a timer and check for data on every
|
|
timeout. */
|
|
/*ws_log(NULL, LOG_LEVEL_DEBUG, "pipe_input_set_handler: new");*/
|
|
|
|
if (pipe_timer_) {
|
|
disconnect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
|
|
delete pipe_timer_;
|
|
}
|
|
|
|
pipe_timer_ = new QTimer(this);
|
|
connect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
|
|
connect(pipe_timer_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
|
|
pipe_timer_->start(200);
|
|
#else
|
|
if (pipe_notifier_) {
|
|
disconnect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
|
|
delete pipe_notifier_;
|
|
}
|
|
|
|
pipe_notifier_ = new QSocketNotifier(pipe_source_, QSocketNotifier::Read);
|
|
// XXX ui/gtk/gui_utils.c sets the encoding. Do we need to do the same?
|
|
connect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
|
|
connect(pipe_notifier_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
|
|
#endif
|
|
}
|
|
|
|
bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
|
|
|
|
// The user typed some text. Start filling in a filter.
|
|
// We may need to be more choosy here. We just need to catch events for the packet list,
|
|
// proto tree, and main welcome widgets.
|
|
if (event->type() == QEvent::KeyPress) {
|
|
QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
|
|
if (kevt->text().length() > 0 && kevt->text()[0].isPrint() &&
|
|
!(kevt->modifiers() & Qt::ControlModifier)) {
|
|
df_combo_box_->lineEdit()->insert(kevt->text());
|
|
df_combo_box_->lineEdit()->setFocus();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return QMainWindow::eventFilter(obj, event);
|
|
}
|
|
|
|
bool MainWindow::event(QEvent *event)
|
|
{
|
|
switch (event->type()) {
|
|
case QEvent::ApplicationPaletteChange:
|
|
initMainToolbarIcons();
|
|
break;
|
|
default:
|
|
break;
|
|
|
|
}
|
|
return QMainWindow::event(event);
|
|
}
|
|
|
|
void MainWindow::keyPressEvent(QKeyEvent *event) {
|
|
|
|
// Explicitly focus on the display filter combo.
|
|
if (event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_Slash) {
|
|
df_combo_box_->setFocus(Qt::ShortcutFocusReason);
|
|
return;
|
|
}
|
|
|
|
if (wsApp->focusWidget() == main_ui_->goToLineEdit) {
|
|
if (event->modifiers() == Qt::NoModifier) {
|
|
if (event->key() == Qt::Key_Escape) {
|
|
on_goToCancel_clicked();
|
|
} else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
|
|
on_goToGo_clicked();
|
|
}
|
|
}
|
|
return; // goToLineEdit didn't want it and we don't either.
|
|
}
|
|
|
|
// Move up & down the packet list.
|
|
if (event->key() == Qt::Key_F7) {
|
|
packet_list_->goPreviousPacket();
|
|
} else if (event->key() == Qt::Key_F8) {
|
|
packet_list_->goNextPacket();
|
|
}
|
|
|
|
// Move along, citizen.
|
|
QMainWindow::keyPressEvent(event);
|
|
}
|
|
|
|
void MainWindow::closeEvent(QCloseEvent *event) {
|
|
saveWindowGeometry();
|
|
|
|
/* If we're in the middle of stopping a capture, don't do anything;
|
|
the user can try deleting the window after the capture stops. */
|
|
if (capture_stopping_) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
QString before_what(tr(" before quitting"));
|
|
if (!testCaptureFileClose(before_what, Quit)) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
if (capture_options_dialog_) capture_options_dialog_->close();
|
|
#endif
|
|
// Make sure we kill any open dumpcap processes.
|
|
delete welcome_page_;
|
|
|
|
// One of the many places we assume one main window.
|
|
if (!wsApp->isInitialized()) {
|
|
// If we're still initializing, QCoreApplication::quit() won't
|
|
// exit properly because we are not in the event loop. This
|
|
// means that the application won't clean up after itself. We
|
|
// might want to call wsApp->processEvents() during startup
|
|
// instead so that we can do a normal exit here.
|
|
exit(0);
|
|
}
|
|
wsApp->quit();
|
|
// When the main loop is not yet running (i.e. when openCaptureFile is
|
|
// executing in main.cpp), the above quit action has no effect.
|
|
// Schedule a quit action for the next execution of the main loop.
|
|
QMetaObject::invokeMethod(wsApp, "quit", Qt::QueuedConnection);
|
|
}
|
|
|
|
// XXX On windows the drag description is "Copy". It should be "Open" or
|
|
// "Merge" as appropriate. It looks like we need access to IDataObject in
|
|
// order to set DROPDESCRIPTION.
|
|
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
|
|
{
|
|
if (!event->mimeData()->hasUrls())
|
|
{
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
if (!main_ui_->actionFileOpen->isEnabled()) {
|
|
// We could alternatively call setAcceptDrops(!capture_in_progress)
|
|
// in setMenusForCaptureInProgress but that wouldn't provide feedback.
|
|
|
|
wsApp->pushStatus(WiresharkApplication::TemporaryStatus, tr("Unable to drop files during capture."));
|
|
event->setDropAction(Qt::IgnoreAction);
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
bool have_files = false;
|
|
foreach(QUrl drag_url, event->mimeData()->urls()) {
|
|
if (!drag_url.toLocalFile().isEmpty()) {
|
|
have_files = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (have_files) {
|
|
event->acceptProposedAction();
|
|
}
|
|
}
|
|
|
|
void MainWindow::dropEvent(QDropEvent *event)
|
|
{
|
|
if (!event->mimeData()->hasUrls())
|
|
{
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
QList<QByteArray> local_files;
|
|
int max_dropped_files = 100; // Arbitrary
|
|
|
|
foreach(QUrl drop_url, event->mimeData()->urls()) {
|
|
QString drop_file = drop_url.toLocalFile();
|
|
if (!drop_file.isEmpty()) {
|
|
local_files << drop_file.toUtf8();
|
|
if (local_files.size() >= max_dropped_files) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
event->acceptProposedAction();
|
|
|
|
if (local_files.size() < 1) {
|
|
event->ignore();
|
|
return;
|
|
}
|
|
|
|
event->accept();
|
|
|
|
if (local_files.size() == 1) {
|
|
openCaptureFile(local_files.at(0));
|
|
return;
|
|
}
|
|
|
|
const char **in_filenames = g_new(const char *, local_files.size());
|
|
char *tmpname = NULL;
|
|
|
|
for (int i = 0; i < local_files.size(); i++) {
|
|
in_filenames[i] = local_files.at(i).constData();
|
|
}
|
|
|
|
/* merge the files in chronological order */
|
|
if (cf_merge_files_to_tempfile(this, &tmpname, local_files.size(),
|
|
in_filenames,
|
|
wtap_pcapng_file_type_subtype(),
|
|
FALSE) == CF_OK) {
|
|
/* Merge succeeded; close the currently-open file and try
|
|
to open the merged capture file. */
|
|
openCaptureFile(tmpname, QString(), WTAP_TYPE_AUTO, TRUE);
|
|
}
|
|
|
|
g_free(tmpname);
|
|
g_free(in_filenames);
|
|
}
|
|
|
|
// Apply recent settings to the main window geometry.
|
|
// We haven't loaded the preferences at this point so we assume that the
|
|
// position and size preference are enabled.
|
|
// Note we might end up with unexpected screen geometries if the user
|
|
// unplugs or plugs in a monitor:
|
|
// https://bugreports.qt.io/browse/QTBUG-44213
|
|
void MainWindow::loadWindowGeometry()
|
|
{
|
|
int min_sensible_dimension = 200;
|
|
|
|
#ifndef Q_OS_MAC
|
|
if (recent.gui_geometry_main_maximized) {
|
|
setWindowState(Qt::WindowMaximized);
|
|
} else
|
|
#endif
|
|
{
|
|
QRect recent_geom(recent.gui_geometry_main_x, recent.gui_geometry_main_y,
|
|
recent.gui_geometry_main_width, recent.gui_geometry_main_height);
|
|
if (!rect_on_screen(recent_geom)) {
|
|
// We're not visible on any screens. See if we can move onscreen
|
|
// without resizing.
|
|
recent_geom.moveTo(50, 50); // recent.c defaults to 20.
|
|
}
|
|
|
|
if (!rect_on_screen(recent_geom)) {
|
|
// Give up and use the default geometry.
|
|
return;
|
|
}
|
|
|
|
// if (prefs.gui_geometry_save_position) {
|
|
move(recent_geom.topLeft());
|
|
// }
|
|
|
|
if (// prefs.gui_geometry_save_size &&
|
|
recent_geom.width() > min_sensible_dimension &&
|
|
recent_geom.height() > min_sensible_dimension) {
|
|
resize(recent_geom.size());
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveWindowGeometry()
|
|
{
|
|
if (prefs.gui_geometry_save_position) {
|
|
recent.gui_geometry_main_x = pos().x();
|
|
recent.gui_geometry_main_y = pos().y();
|
|
}
|
|
|
|
if (prefs.gui_geometry_save_size) {
|
|
recent.gui_geometry_main_width = size().width();
|
|
recent.gui_geometry_main_height = size().height();
|
|
}
|
|
|
|
if (prefs.gui_geometry_save_maximized) {
|
|
// On macOS this is false when it shouldn't be
|
|
recent.gui_geometry_main_maximized = isMaximized();
|
|
}
|
|
|
|
if (master_split_.sizes().length() > 0) {
|
|
recent.gui_geometry_main_upper_pane = master_split_.sizes()[0];
|
|
}
|
|
|
|
if (master_split_.sizes().length() > 2) {
|
|
recent.gui_geometry_main_lower_pane = master_split_.sizes()[1];
|
|
} else if (extra_split_.sizes().length() > 0) {
|
|
recent.gui_geometry_main_lower_pane = extra_split_.sizes()[0];
|
|
}
|
|
}
|
|
|
|
// Our event loop becomes nested whenever we call update_progress_dlg, which
|
|
// includes several places in file.c. The GTK+ UI stays out of trouble by
|
|
// showing a modal progress dialog. We attempt to do the equivalent below by
|
|
// disabling parts of the main window. At a minumum the ProgressFrame in the
|
|
// main status bar must remain accessible.
|
|
//
|
|
// We might want to do this any time the main status bar progress frame is
|
|
// shown and hidden.
|
|
void MainWindow::freeze()
|
|
{
|
|
freeze_focus_ = wsApp->focusWidget();
|
|
|
|
// XXX Alternatively we could just disable and enable the main menu.
|
|
for (int i = 0; i < freeze_actions_.size(); i++) {
|
|
QAction *action = freeze_actions_[i].first;
|
|
freeze_actions_[i].second = action->isEnabled();
|
|
action->setEnabled(false);
|
|
}
|
|
main_ui_->centralWidget->setEnabled(false);
|
|
}
|
|
|
|
void MainWindow::thaw()
|
|
{
|
|
main_ui_->centralWidget->setEnabled(true);
|
|
for (int i = 0; i < freeze_actions_.size(); i++) {
|
|
freeze_actions_[i].first->setEnabled(freeze_actions_[i].second);
|
|
}
|
|
|
|
if (freeze_focus_) freeze_focus_->setFocus();
|
|
freeze_focus_ = NULL;
|
|
}
|
|
|
|
void MainWindow::mergeCaptureFile()
|
|
{
|
|
QString file_name = "";
|
|
QString read_filter = "";
|
|
dfilter_t *rfcode = NULL;
|
|
int err;
|
|
|
|
if (!capture_file_.capFile())
|
|
return;
|
|
|
|
if (prefs.gui_ask_unsaved) {
|
|
if (cf_has_unsaved_data(capture_file_.capFile())) {
|
|
QMessageBox msg_dialog;
|
|
gchar *display_basename;
|
|
int response;
|
|
|
|
msg_dialog.setIcon(QMessageBox::Question);
|
|
/* This file has unsaved data; ask the user whether to save
|
|
the capture. */
|
|
if (capture_file_.capFile()->is_tempfile) {
|
|
msg_dialog.setText(tr("Save packets before merging?"));
|
|
msg_dialog.setInformativeText(tr("A temporary capture file can't be merged."));
|
|
} else {
|
|
/*
|
|
* Format the message.
|
|
*/
|
|
display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
|
|
msg_dialog.setText(QString(tr("Save changes in \"%1\" before merging?")).arg(display_basename));
|
|
g_free(display_basename);
|
|
msg_dialog.setInformativeText(tr("Changes must be saved before the files can be merged."));
|
|
}
|
|
|
|
msg_dialog.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
|
|
msg_dialog.setDefaultButton(QMessageBox::Save);
|
|
|
|
response = msg_dialog.exec();
|
|
|
|
switch (response) {
|
|
|
|
case QMessageBox::Save:
|
|
/* Save the file but don't close it */
|
|
saveCaptureFile(capture_file_.capFile(), false);
|
|
break;
|
|
|
|
case QMessageBox::Cancel:
|
|
default:
|
|
/* Don't do the merge. */
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (;;) {
|
|
CaptureFileDialog merge_dlg(this, capture_file_.capFile(), read_filter);
|
|
int file_type;
|
|
cf_status_t merge_status;
|
|
char *in_filenames[2];
|
|
char *tmpname;
|
|
|
|
if (merge_dlg.merge(file_name)) {
|
|
gchar *err_msg;
|
|
|
|
if (!dfilter_compile(qUtf8Printable(read_filter), &rfcode, &err_msg)) {
|
|
/* Not valid. Tell the user, and go back and run the file
|
|
selection box again once they dismiss the alert. */
|
|
// Similar to commandline_info.jfilter section in main().
|
|
QMessageBox::warning(this, tr("Invalid Read Filter"),
|
|
QString(tr("The filter expression %1 isn't a valid read filter. (%2).").arg(read_filter, err_msg)),
|
|
QMessageBox::Ok);
|
|
g_free(err_msg);
|
|
continue;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
file_type = capture_file_.capFile()->cd_t;
|
|
|
|
/* Try to merge or append the two files */
|
|
if (merge_dlg.mergeType() == 0) {
|
|
/* chronological order */
|
|
in_filenames[0] = g_strdup(capture_file_.capFile()->filename);
|
|
in_filenames[1] = qstring_strdup(file_name);
|
|
merge_status = cf_merge_files_to_tempfile(this, &tmpname, 2, in_filenames, file_type, FALSE);
|
|
} else if (merge_dlg.mergeType() <= 0) {
|
|
/* prepend file */
|
|
in_filenames[0] = qstring_strdup(file_name);
|
|
in_filenames[1] = g_strdup(capture_file_.capFile()->filename);
|
|
merge_status = cf_merge_files_to_tempfile(this, &tmpname, 2, in_filenames, file_type, TRUE);
|
|
} else {
|
|
/* append file */
|
|
in_filenames[0] = g_strdup(capture_file_.capFile()->filename);
|
|
in_filenames[1] = qstring_strdup(file_name);
|
|
merge_status = cf_merge_files_to_tempfile(this, &tmpname, 2, in_filenames, file_type, TRUE);
|
|
}
|
|
|
|
g_free(in_filenames[0]);
|
|
g_free(in_filenames[1]);
|
|
|
|
if (merge_status != CF_OK) {
|
|
dfilter_free(rfcode);
|
|
g_free(tmpname);
|
|
continue;
|
|
}
|
|
|
|
cf_close(capture_file_.capFile());
|
|
|
|
/* Try to open the merged capture file. */
|
|
CaptureFile::globalCapFile()->window = this;
|
|
if (cf_open(CaptureFile::globalCapFile(), tmpname, WTAP_TYPE_AUTO, TRUE /* temporary file */, &err) != CF_OK) {
|
|
/* We couldn't open it; fail. */
|
|
CaptureFile::globalCapFile()->window = NULL;
|
|
dfilter_free(rfcode);
|
|
g_free(tmpname);
|
|
return;
|
|
}
|
|
|
|
/* Attach the new read filter to "cf" ("cf_open()" succeeded, so
|
|
it closed the previous capture file, and thus destroyed any
|
|
previous read filter attached to "cf"). */
|
|
cf_set_rfcode(CaptureFile::globalCapFile(), rfcode);
|
|
|
|
switch (cf_read(CaptureFile::globalCapFile(), FALSE)) {
|
|
|
|
case CF_READ_OK:
|
|
case CF_READ_ERROR:
|
|
/* Just because we got an error, that doesn't mean we were unable
|
|
to read any of the file; we handle what we could get from the
|
|
file. */
|
|
break;
|
|
|
|
case CF_READ_ABORTED:
|
|
/* The user bailed out of re-reading the capture file; the
|
|
capture file has been closed - just free the capture file name
|
|
string and return (without changing the last containing
|
|
directory). */
|
|
g_free(tmpname);
|
|
return;
|
|
}
|
|
|
|
/* Save the name of the containing directory specified in the path name. */
|
|
wsApp->setLastOpenDirFromFilename(tmpname);
|
|
g_free(tmpname);
|
|
main_ui_->statusBar->showExpert();
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
void MainWindow::importCaptureFile() {
|
|
ImportTextDialog import_dlg;
|
|
|
|
QString before_what(tr(" before importing a capture"));
|
|
if (!testCaptureFileClose(before_what))
|
|
return;
|
|
|
|
import_dlg.exec();
|
|
|
|
if (import_dlg.result() != QDialog::Accepted) {
|
|
showWelcome();
|
|
return;
|
|
}
|
|
|
|
openCaptureFile(import_dlg.capfileName());
|
|
}
|
|
|
|
bool MainWindow::saveCaptureFile(capture_file *cf, bool dont_reopen) {
|
|
QString file_name;
|
|
gboolean discard_comments;
|
|
|
|
if (cf->is_tempfile) {
|
|
/* This is a temporary capture file, so saving it means saving
|
|
it to a permanent file. Prompt the user for a location
|
|
to which to save it. Don't require that the file format
|
|
support comments - if it's a temporary capture file, it's
|
|
probably pcapng, which supports comments and, if it's
|
|
not pcapng, let the user decide what they want to do
|
|
if they've added comments. */
|
|
return saveAsCaptureFile(cf, FALSE, dont_reopen);
|
|
} else {
|
|
if (cf->unsaved_changes) {
|
|
cf_write_status_t status;
|
|
|
|
/* This is not a temporary capture file, but it has unsaved
|
|
changes, so saving it means doing a "safe save" on top
|
|
of the existing file, in the same format - no UI needed
|
|
unless the file has comments and the file's format doesn't
|
|
support them.
|
|
|
|
If the file has comments, does the file's format support them?
|
|
If not, ask the user whether they want to discard the comments
|
|
or choose a different format. */
|
|
switch (CaptureFileDialog::checkSaveAsWithComments(this, cf, cf->cd_t)) {
|
|
|
|
case SAVE:
|
|
/* The file can be saved in the specified format as is;
|
|
just drive on and save in the format they selected. */
|
|
discard_comments = FALSE;
|
|
break;
|
|
|
|
case SAVE_WITHOUT_COMMENTS:
|
|
/* The file can't be saved in the specified format as is,
|
|
but it can be saved without the comments, and the user
|
|
said "OK, discard the comments", so save it in the
|
|
format they specified without the comments. */
|
|
discard_comments = TRUE;
|
|
break;
|
|
|
|
case SAVE_IN_ANOTHER_FORMAT:
|
|
/* There are file formats in which we can save this that
|
|
support comments, and the user said not to delete the
|
|
comments. Do a "Save As" so the user can select
|
|
one of those formats and choose a file name. */
|
|
return saveAsCaptureFile(cf, TRUE, dont_reopen);
|
|
|
|
case CANCELLED:
|
|
/* The user said "forget it". Just return. */
|
|
return false;
|
|
|
|
default:
|
|
/* Squelch warnings that discard_comments is being used
|
|
uninitialized. */
|
|
ws_assert_not_reached();
|
|
return false;
|
|
}
|
|
|
|
/* XXX - cf->filename might get freed out from under us, because
|
|
the code path through which cf_save_records() goes currently
|
|
closes the current file and then opens and reloads the saved file,
|
|
so make a copy and free it later. */
|
|
file_name = cf->filename;
|
|
status = cf_save_records(cf, qUtf8Printable(file_name), cf->cd_t, cf->compression_type,
|
|
discard_comments, dont_reopen);
|
|
switch (status) {
|
|
|
|
case CF_WRITE_OK:
|
|
/* The save succeeded; we're done.
|
|
If we discarded comments, redraw the packet list to reflect
|
|
any packets that no longer have comments. */
|
|
if (discard_comments)
|
|
packet_list_queue_draw();
|
|
|
|
cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
|
|
updateForUnsavedChanges(); // we update the title bar to remove the *
|
|
break;
|
|
|
|
case CF_WRITE_ERROR:
|
|
/* The write failed.
|
|
XXX - OK, what do we do now? Let them try a
|
|
"Save As", in case they want to try to save to a
|
|
different directory or file system? */
|
|
break;
|
|
|
|
case CF_WRITE_ABORTED:
|
|
/* The write was aborted; just drive on. */
|
|
return false;
|
|
}
|
|
}
|
|
/* Otherwise just do nothing. */
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MainWindow::saveAsCaptureFile(capture_file *cf, bool must_support_comments, bool dont_reopen) {
|
|
QString file_name = "";
|
|
int file_type;
|
|
wtap_compression_type compression_type;
|
|
cf_write_status_t status;
|
|
gchar *dirname;
|
|
gboolean discard_comments = FALSE;
|
|
|
|
if (!cf) {
|
|
return false;
|
|
}
|
|
|
|
for (;;) {
|
|
CaptureFileDialog save_as_dlg(this, cf);
|
|
|
|
/* If the file has comments, does the format the user selected
|
|
support them? If not, ask the user whether they want to
|
|
discard the comments or choose a different format. */
|
|
switch (save_as_dlg.saveAs(file_name, must_support_comments)) {
|
|
|
|
case SAVE:
|
|
/* The file can be saved in the specified format as is;
|
|
just drive on and save in the format they selected. */
|
|
discard_comments = FALSE;
|
|
break;
|
|
|
|
case SAVE_WITHOUT_COMMENTS:
|
|
/* The file can't be saved in the specified format as is,
|
|
but it can be saved without the comments, and the user
|
|
said "OK, discard the comments", so save it in the
|
|
format they specified without the comments. */
|
|
discard_comments = TRUE;
|
|
break;
|
|
|
|
case SAVE_IN_ANOTHER_FORMAT:
|
|
/* There are file formats in which we can save this that
|
|
support comments, and the user said not to delete the
|
|
comments. The combo box of file formats has had the
|
|
formats that don't support comments trimmed from it,
|
|
so run the dialog again, to let the user decide
|
|
whether to save in one of those formats or give up. */
|
|
must_support_comments = TRUE;
|
|
continue;
|
|
|
|
case CANCELLED:
|
|
/* The user said "forget it". Just get rid of the dialog box
|
|
and return. */
|
|
return false;
|
|
}
|
|
file_type = save_as_dlg.selectedFileType();
|
|
if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) {
|
|
/* This "should not happen". */
|
|
QMessageBox msg_dialog;
|
|
|
|
msg_dialog.setIcon(QMessageBox::Critical);
|
|
msg_dialog.setText(tr("Unknown file type returned by merge dialog."));
|
|
msg_dialog.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
|
|
msg_dialog.exec();
|
|
return false;
|
|
}
|
|
compression_type = save_as_dlg.compressionType();
|
|
|
|
#ifdef Q_OS_WIN
|
|
// the Windows dialog does not fixup extensions, do it manually here.
|
|
fileAddExtension(file_name, file_type, compression_type);
|
|
#endif // Q_OS_WIN
|
|
|
|
//#ifndef _WIN32
|
|
// /* If the file exists and it's user-immutable or not writable,
|
|
// ask the user whether they want to override that. */
|
|
// if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) {
|
|
// /* They don't. Let them try another file name or cancel. */
|
|
// continue;
|
|
// }
|
|
//#endif
|
|
|
|
/* Attempt to save the file */
|
|
status = cf_save_records(cf, qUtf8Printable(file_name), file_type, compression_type,
|
|
discard_comments, dont_reopen);
|
|
switch (status) {
|
|
|
|
case CF_WRITE_OK:
|
|
/* The save succeeded; we're done. */
|
|
/* Save the directory name for future file dialogs. */
|
|
dirname = qstring_strdup(file_name); /* Overwrites cf_name */
|
|
set_last_open_dir(get_dirname(dirname));
|
|
g_free(dirname);
|
|
/* If we discarded comments, redraw the packet list to reflect
|
|
any packets that no longer have comments. */
|
|
if (discard_comments)
|
|
packet_list_queue_draw();
|
|
|
|
cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
|
|
updateForUnsavedChanges(); // we update the title bar to remove the *
|
|
/* Add this filename to the list of recent files in the "Recent Files" submenu */
|
|
add_menu_recent_capture_file(qUtf8Printable(file_name));
|
|
return true;
|
|
|
|
case CF_WRITE_ERROR:
|
|
/* The save failed; let the user try again. */
|
|
continue;
|
|
|
|
case CF_WRITE_ABORTED:
|
|
/* The user aborted the save; just return. */
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void MainWindow::exportSelectedPackets() {
|
|
QString file_name = "";
|
|
int file_type;
|
|
wtap_compression_type compression_type;
|
|
packet_range_t range;
|
|
cf_write_status_t status;
|
|
gchar *dirname;
|
|
bool discard_comments = false;
|
|
|
|
if (!capture_file_.capFile())
|
|
return;
|
|
|
|
/* Init the packet range */
|
|
packet_range_init(&range, capture_file_.capFile());
|
|
range.process_filtered = TRUE;
|
|
range.include_dependents = TRUE;
|
|
|
|
QList<int> rows = packet_list_->selectedRows(true);
|
|
|
|
QStringList entries;
|
|
foreach (int row, rows)
|
|
entries << QString::number(row);
|
|
QString selRange = entries.join(",");
|
|
|
|
for (;;) {
|
|
CaptureFileDialog esp_dlg(this, capture_file_.capFile());
|
|
|
|
/* If the file has comments, does the format the user selected
|
|
support them? If not, ask the user whether they want to
|
|
discard the comments or choose a different format. */
|
|
switch (esp_dlg.exportSelectedPackets(file_name, &range, selRange)) {
|
|
|
|
case SAVE:
|
|
/* The file can be saved in the specified format as is;
|
|
just drive on and save in the format they selected. */
|
|
discard_comments = FALSE;
|
|
break;
|
|
|
|
case SAVE_WITHOUT_COMMENTS:
|
|
/* The file can't be saved in the specified format as is,
|
|
but it can be saved without the comments, and the user
|
|
said "OK, discard the comments", so save it in the
|
|
format they specified without the comments. */
|
|
discard_comments = TRUE;
|
|
break;
|
|
|
|
case SAVE_IN_ANOTHER_FORMAT:
|
|
/* There are file formats in which we can save this that
|
|
support comments, and the user said not to delete the
|
|
comments. The combo box of file formats has had the
|
|
formats that don't support comments trimmed from it,
|
|
so run the dialog again, to let the user decide
|
|
whether to save in one of those formats or give up. */
|
|
continue;
|
|
|
|
case CANCELLED:
|
|
/* The user said "forget it". Just get rid of the dialog box
|
|
and return. */
|
|
goto cleanup;
|
|
}
|
|
|
|
/*
|
|
* Check that we're not going to save on top of the current
|
|
* capture file.
|
|
* We do it here so we catch all cases ...
|
|
* Unfortunately, the file requester gives us an absolute file
|
|
* name and the read file name may be relative (if supplied on
|
|
* the command line). From Joerg Mayer.
|
|
*/
|
|
if (files_identical(capture_file_.capFile()->filename, qUtf8Printable(file_name))) {
|
|
QMessageBox msg_box;
|
|
gchar *display_basename = g_filename_display_basename(qUtf8Printable(file_name));
|
|
|
|
msg_box.setIcon(QMessageBox::Critical);
|
|
msg_box.setText(QString(tr("Unable to export to \"%1\".").arg(display_basename)));
|
|
msg_box.setInformativeText(tr("You cannot export packets to the current capture file."));
|
|
msg_box.setStandardButtons(QMessageBox::Ok);
|
|
msg_box.setDefaultButton(QMessageBox::Ok);
|
|
msg_box.exec();
|
|
g_free(display_basename);
|
|
continue;
|
|
}
|
|
|
|
file_type = esp_dlg.selectedFileType();
|
|
if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) {
|
|
/* This "should not happen". */
|
|
QMessageBox msg_box;
|
|
|
|
msg_box.setIcon(QMessageBox::Critical);
|
|
msg_box.setText(tr("Unknown file type returned by export dialog."));
|
|
msg_box.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
|
|
msg_box.exec();
|
|
goto cleanup;
|
|
}
|
|
compression_type = esp_dlg.compressionType();
|
|
#ifdef Q_OS_WIN
|
|
// the Windows dialog does not fixup extensions, do it manually here.
|
|
fileAddExtension(file_name, file_type, compression_type);
|
|
#endif // Q_OS_WIN
|
|
|
|
//#ifndef _WIN32
|
|
// /* If the file exists and it's user-immutable or not writable,
|
|
// ask the user whether they want to override that. */
|
|
// if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) {
|
|
// /* They don't. Let them try another file name or cancel. */
|
|
// continue;
|
|
// }
|
|
//#endif
|
|
|
|
/* Attempt to save the file */
|
|
status = cf_export_specified_packets(capture_file_.capFile(), qUtf8Printable(file_name), &range, file_type, compression_type);
|
|
switch (status) {
|
|
|
|
case CF_WRITE_OK:
|
|
/* The save succeeded; we're done. */
|
|
/* Save the directory name for future file dialogs. */
|
|
dirname = qstring_strdup(file_name); /* Overwrites cf_name */
|
|
set_last_open_dir(get_dirname(dirname));
|
|
g_free(dirname);
|
|
/* If we discarded comments, redraw the packet list to reflect
|
|
any packets that no longer have comments. */
|
|
if (discard_comments)
|
|
packet_list_queue_draw();
|
|
/* Add this filename to the list of recent files in the "Recent Files" submenu */
|
|
add_menu_recent_capture_file(qUtf8Printable(file_name));
|
|
goto cleanup;
|
|
|
|
case CF_WRITE_ERROR:
|
|
/* The save failed; let the user try again. */
|
|
continue;
|
|
|
|
case CF_WRITE_ABORTED:
|
|
/* The user aborted the save; just return. */
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
cleanup:
|
|
packet_range_cleanup(&range);
|
|
}
|
|
|
|
void MainWindow::exportDissections(export_type_e export_type) {
|
|
capture_file *cf = capture_file_.capFile();
|
|
g_return_if_fail(cf);
|
|
|
|
QList<int> rows = packet_list_->selectedRows(true);
|
|
|
|
QStringList entries;
|
|
foreach (int row, rows)
|
|
entries << QString::number(row);
|
|
QString selRange = entries.join(",");
|
|
|
|
ExportDissectionDialog *ed_dlg = new ExportDissectionDialog(this, cf, export_type, selRange);
|
|
ed_dlg->setWindowModality(Qt::ApplicationModal);
|
|
ed_dlg->setAttribute(Qt::WA_DeleteOnClose);
|
|
ed_dlg->show();
|
|
}
|
|
|
|
#ifdef Q_OS_WIN
|
|
/*
|
|
* Ensure that:
|
|
*
|
|
* If the file is to be compressed:
|
|
*
|
|
* if there is a set of extensions used by the file type to be used,
|
|
* the file name has one of those extensions followed by the extension
|
|
* for the compression type to be used;
|
|
*
|
|
* otherwise, the file name has the extension for the compression type
|
|
* to be used;
|
|
*
|
|
* otherwise:
|
|
*
|
|
* if there is a set of extensions used by the file type to be used,
|
|
* the file name has one of those extensions.
|
|
*/
|
|
void MainWindow::fileAddExtension(QString &file_name, int file_type, wtap_compression_type compression_type) {
|
|
QString file_name_lower;
|
|
GSList *extensions_list;
|
|
const char *compressed_file_extension;
|
|
gboolean add_extension_for_file_type;
|
|
|
|
/* Lower-case the file name, so the extension matching is case-insensitive. */
|
|
file_name_lower = file_name.toLower();
|
|
|
|
/* Get a list of all extensions used for this file type; don't
|
|
include the ones with compression type extensions, as we
|
|
only want to check for the extension for the compression
|
|
type we'll be using. */
|
|
extensions_list = wtap_get_file_extensions_list(file_type, FALSE);
|
|
|
|
/* Get the extension for the compression type we'll be using;
|
|
NULL is returned if the type isn't supported or compression
|
|
is not being done. */
|
|
compressed_file_extension = wtap_compression_type_extension(compression_type);
|
|
|
|
if (extensions_list != NULL) {
|
|
GSList *extension;
|
|
|
|
/* This file type has one or more extensions.
|
|
Start out assuming we need to add the default one. */
|
|
add_extension_for_file_type = TRUE;
|
|
|
|
/* OK, see if the file has one of those extensions, followed
|
|
by the appropriate compression type extension if it's to be
|
|
compressed. */
|
|
for (extension = extensions_list; extension != NULL;
|
|
extension = g_slist_next(extension)) {
|
|
QString file_suffix = QString(".") + (char *)extension->data;
|
|
if (compressed_file_extension != NULL)
|
|
file_suffix += QString(".") + compressed_file_extension;
|
|
if (file_name_lower.endsWith(file_suffix)) {
|
|
/*
|
|
* The file name has one of the extensions for this file
|
|
* type, followed by a compression type extension if
|
|
* appropriate, so we don't need to add an extension for
|
|
* the file type or the compression type.
|
|
*/
|
|
add_extension_for_file_type = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* We have no extensions for this file type. Just check
|
|
to see if we need to add an extension for the compressed
|
|
file type.
|
|
|
|
Start out assuming we do. */
|
|
add_extension_for_file_type = TRUE;
|
|
if (compressed_file_extension != NULL) {
|
|
QString file_suffix = QString(".") + compressed_file_extension;
|
|
if (file_name_lower.endsWith(file_suffix)) {
|
|
/*
|
|
* The file name has the appropriate compressed file extension,
|
|
* so we don't need to add an extension for the compression
|
|
* type.
|
|
*/
|
|
add_extension_for_file_type = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If we need to add an extension for the file type or compressed
|
|
* file type, do so.
|
|
*/
|
|
if (add_extension_for_file_type) {
|
|
if (wtap_default_file_extension(file_type) != NULL) {
|
|
/* This file type has a default extension; append it. */
|
|
file_name += QString(".") + wtap_default_file_extension(file_type);
|
|
}
|
|
if (compression_type != WTAP_UNCOMPRESSED) {
|
|
/*
|
|
* The file is to be compressed, so append the extension for
|
|
* its compression type.
|
|
*/
|
|
file_name += QString(".") + compressed_file_extension;
|
|
}
|
|
}
|
|
}
|
|
#endif // Q_OS_WIN
|
|
|
|
bool MainWindow::testCaptureFileClose(QString before_what, FileCloseContext context) {
|
|
bool capture_in_progress = false;
|
|
bool do_close_file = false;
|
|
|
|
if (!capture_file_.capFile() || capture_file_.capFile()->state == FILE_CLOSED)
|
|
return true; /* Already closed, nothing to do */
|
|
|
|
if (capture_file_.capFile()->read_lock) {
|
|
/*
|
|
* If the file is being redissected, we cannot stop the capture since
|
|
* that would crash and burn "cf_read", so stop early. Ideally all
|
|
* callers should be modified to check this condition and act
|
|
* accordingly (ignore action or queue it up), so print a warning.
|
|
*/
|
|
ws_warning("Refusing to close \"%s\" which is being read.", capture_file_.capFile()->filename);
|
|
return false;
|
|
}
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
if (capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
|
|
/*
|
|
* This (FILE_READ_IN_PROGRESS) is true if we're reading a capture file
|
|
* *or* if we're doing a live capture. From the capture file itself we
|
|
* cannot differentiate the cases, so check the current capture session.
|
|
*/
|
|
capture_in_progress = captureSession()->state != CAPTURE_STOPPED;
|
|
}
|
|
#endif
|
|
|
|
if (prefs.gui_ask_unsaved) {
|
|
if (cf_has_unsaved_data(capture_file_.capFile())) {
|
|
QMessageBox msg_dialog;
|
|
QString question;
|
|
QString infotext;
|
|
QPushButton *save_button;
|
|
QPushButton *discard_button;
|
|
|
|
msg_dialog.setIcon(QMessageBox::Question);
|
|
msg_dialog.setWindowTitle("Unsaved packets" UTF8_HORIZONTAL_ELLIPSIS);
|
|
|
|
/* This file has unsaved data or there's a capture in
|
|
progress; ask the user whether to save the data. */
|
|
if (capture_in_progress && context != Restart) {
|
|
question = tr("Do you want to stop the capture and save the captured packets%1?").arg(before_what);
|
|
infotext = tr("Your captured packets will be lost if you don't save them.");
|
|
} else if (capture_file_.capFile()->is_tempfile) {
|
|
if (context == Reload) {
|
|
// Reloading a tempfile will keep the packets, so this is not unsaved packets
|
|
question = tr("Do you want to save the changes you've made%1?").arg(before_what);
|
|
infotext = tr("Your changes will be lost if you don't save them.");
|
|
} else {
|
|
question = tr("Do you want to save the captured packets%1?").arg(before_what);
|
|
infotext = tr("Your captured packets will be lost if you don't save them.");
|
|
}
|
|
} else {
|
|
// No capture in progress and not a tempfile, so this is not unsaved packets
|
|
gchar *display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
|
|
question = tr("Do you want to save the changes you've made to the capture file \"%1\"%2?").arg(display_basename, before_what);
|
|
infotext = tr("Your changes will be lost if you don't save them.");
|
|
g_free(display_basename);
|
|
}
|
|
|
|
msg_dialog.setText(question);
|
|
msg_dialog.setInformativeText(infotext);
|
|
|
|
// XXX Text comes from ui/gtk/stock_icons.[ch]
|
|
// Note that the button roles differ from the GTK+ version.
|
|
// Cancel = RejectRole
|
|
// Save = AcceptRole
|
|
// Don't Save = DestructiveRole
|
|
msg_dialog.addButton(QMessageBox::Cancel);
|
|
|
|
if (capture_in_progress) {
|
|
QString save_button_text;
|
|
if (context == Restart) {
|
|
save_button_text = tr("Save before Continue");
|
|
} else {
|
|
save_button_text = tr("Stop and Save");
|
|
}
|
|
save_button = msg_dialog.addButton(save_button_text, QMessageBox::AcceptRole);
|
|
} else {
|
|
save_button = msg_dialog.addButton(QMessageBox::Save);
|
|
}
|
|
msg_dialog.setDefaultButton(save_button);
|
|
|
|
QString discard_button_text;
|
|
if (capture_in_progress) {
|
|
switch (context) {
|
|
case Quit:
|
|
discard_button_text = tr("Stop and Quit &without Saving");
|
|
break;
|
|
case Restart:
|
|
discard_button_text = tr("Continue &without Saving");
|
|
break;
|
|
default:
|
|
discard_button_text = tr("Stop and Continue &without Saving");
|
|
break;
|
|
}
|
|
} else {
|
|
switch (context) {
|
|
case Quit:
|
|
discard_button_text = tr("Quit &without Saving");
|
|
break;
|
|
case Restart:
|
|
default:
|
|
discard_button_text = tr("Continue &without Saving");
|
|
break;
|
|
}
|
|
}
|
|
discard_button = msg_dialog.addButton(discard_button_text, QMessageBox::DestructiveRole);
|
|
|
|
#if defined(Q_OS_MAC)
|
|
/*
|
|
* In macOS, the "default button" is not necessarily the
|
|
* button that has the input focus; Enter/Return activates
|
|
* the default button, and the spacebar activates the button
|
|
* that has the input focus, and they might be different
|
|
* buttons.
|
|
*
|
|
* In a "do you want to save" dialog, for example, the
|
|
* "save" button is the default button, and the "don't
|
|
* save" button has the input focus, so you can press
|
|
* Enter/Return to save or space not to save (or Escape
|
|
* to dismiss the dialog).
|
|
*
|
|
* In Qt terms, this means "no auto-default", as auto-default
|
|
* makes the button with the input focus the default button,
|
|
* so that Enter/Return will activate it.
|
|
*/
|
|
QList<QAbstractButton *> buttons = msg_dialog.buttons();
|
|
for (int i = 0; i < buttons.size(); ++i) {
|
|
QPushButton *button = static_cast<QPushButton *>(buttons.at(i));;
|
|
button->setAutoDefault(false);
|
|
}
|
|
|
|
/*
|
|
* It also means that the "don't save" button should be the one
|
|
* initially given the focus.
|
|
*/
|
|
discard_button->setFocus();
|
|
#endif
|
|
|
|
msg_dialog.exec();
|
|
/* According to the Qt doc:
|
|
* when using QMessageBox with custom buttons, exec() function returns an opaque value.
|
|
*
|
|
* Therefore we should use clickedButton() to determine which button was clicked. */
|
|
|
|
if (msg_dialog.clickedButton() == save_button) {
|
|
#ifdef HAVE_LIBPCAP
|
|
/* If there's a capture in progress, we have to stop the capture
|
|
and then do the save. */
|
|
if (capture_in_progress)
|
|
captureStop();
|
|
#endif
|
|
/* Save the file and close it */
|
|
// XXX if no packets were captured, any unsaved comments set by
|
|
// the user are silently discarded because capFile() is null.
|
|
if (capture_file_.capFile() && saveCaptureFile(capture_file_.capFile(), true) == false)
|
|
return false;
|
|
do_close_file = true;
|
|
} else if (msg_dialog.clickedButton() == discard_button) {
|
|
/* Just close the file, discarding changes */
|
|
do_close_file = true;
|
|
} else {
|
|
// cancelButton or some other unspecified button
|
|
return false;
|
|
}
|
|
} else {
|
|
/* Unchanged file or capturing with no packets */
|
|
do_close_file = true;
|
|
}
|
|
} else {
|
|
/* User asked not to be bothered by those prompts, just close it.
|
|
XXX - should that apply only to saving temporary files? */
|
|
do_close_file = true;
|
|
}
|
|
|
|
/*
|
|
* Are we done with this file and should we close the file?
|
|
*/
|
|
if (do_close_file) {
|
|
#ifdef HAVE_LIBPCAP
|
|
/* If there's a capture in progress, we have to stop the capture
|
|
and then do the close. */
|
|
if (capture_in_progress)
|
|
captureStop();
|
|
else if (capture_file_.capFile() && capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
|
|
/*
|
|
* When an offline capture is being read, mark it as aborted.
|
|
* cf_read will be responsible for actually closing the capture.
|
|
*
|
|
* We cannot just invoke cf_close here since cf_read is up in the
|
|
* call chain. (update_progress_dlg can end up processing the Quit
|
|
* event from the user which then ends up here.)
|
|
* See also the above "read_lock" check.
|
|
*/
|
|
capture_file_.capFile()->state = FILE_READ_ABORTED;
|
|
return true;
|
|
}
|
|
#endif
|
|
/* Clear MainWindow file name details */
|
|
gbl_cur_main_window_->setMwFileName("");
|
|
|
|
/* captureStop() will close the file if not having any packets */
|
|
if (capture_file_.capFile() && context != Restart && context != Reload)
|
|
// Don't really close if Restart or Reload
|
|
cf_close(capture_file_.capFile());
|
|
}
|
|
|
|
return true; /* File closed */
|
|
}
|
|
|
|
void MainWindow::captureStop() {
|
|
stopCapture();
|
|
|
|
while (capture_file_.capFile() && capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
|
|
WiresharkApplication::processEvents();
|
|
}
|
|
}
|
|
|
|
void MainWindow::findTextCodecs() {
|
|
const QList<int> mibs = QTextCodec::availableMibs();
|
|
QRegularExpression ibmRegExp("^IBM([0-9]+).*$");
|
|
QRegularExpression iso8859RegExp("^ISO-8859-([0-9]+).*$");
|
|
QRegularExpression windowsRegExp("^WINDOWS-([0-9]+).*$");
|
|
QRegularExpressionMatch match;
|
|
for (int mib : mibs) {
|
|
QTextCodec *codec = QTextCodec::codecForMib(mib);
|
|
QString key = codec->name().toUpper();
|
|
char rank;
|
|
|
|
if (key.localeAwareCompare("IBM") < 0) {
|
|
rank = 1;
|
|
} else if ((match = ibmRegExp.match(key)).hasMatch()) {
|
|
rank = match.captured(1).size(); // Up to 5
|
|
} else if (key.localeAwareCompare("ISO-8859-") < 0) {
|
|
rank = 6;
|
|
} else if ((match = iso8859RegExp.match(key)).hasMatch()) {
|
|
rank = 6 + match.captured(1).size(); // Up to 6 + 2
|
|
} else if (key.localeAwareCompare("WINDOWS-") < 0) {
|
|
rank = 9;
|
|
} else if ((match = windowsRegExp.match(key)).hasMatch()) {
|
|
rank = 9 + match.captured(1).size(); // Up to 9 + 4
|
|
} else {
|
|
rank = 14;
|
|
}
|
|
// This doesn't perfectly well order the IBM codecs because it's
|
|
// annoying to properly place IBM00858 and IBM00924 in the middle of
|
|
// code page numbers not zero padded to 5 digits.
|
|
// We could manipulate the key further to have more commonly used
|
|
// charsets earlier. IANA MIB ordering would be unxpected:
|
|
// https://www.iana.org/assignments/character-sets/character-sets.xml
|
|
// For data about use in HTTP (other protocols can be quite different):
|
|
// https://w3techs.com/technologies/overview/character_encoding
|
|
|
|
key.prepend(char('0' + rank));
|
|
// We use a map here because, due to backwards compatibility,
|
|
// the same QTextCodec may be returned for multiple MIBs, which
|
|
// happens for GBK/GB2312, EUC-KR/windows-949/UHC, and others.
|
|
text_codec_map_.insert(key, codec);
|
|
}
|
|
}
|
|
|
|
void MainWindow::initMainToolbarIcons()
|
|
{
|
|
// Normally 16 px. Reflects current GTK+ behavior and other Windows apps.
|
|
int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize);
|
|
#if !defined(Q_OS_WIN)
|
|
// Force icons to 24x24 for now, otherwise actionFileOpen looks wonky.
|
|
// The macOS HIG specifies 32-pixel icons but they're a little too
|
|
// large IMHO.
|
|
icon_size = icon_size * 3 / 2;
|
|
#endif
|
|
main_ui_->mainToolBar->setIconSize(QSize(icon_size, icon_size));
|
|
|
|
// Toolbar actions. The GNOME HIG says that we should have a menu icon for each
|
|
// toolbar item but that clutters up our menu. Set menu icons sparingly.
|
|
|
|
main_ui_->actionCaptureStart->setIcon(StockIcon("x-capture-start"));
|
|
main_ui_->actionCaptureStop->setIcon(StockIcon("x-capture-stop"));
|
|
main_ui_->actionCaptureRestart->setIcon(StockIcon("x-capture-restart"));
|
|
main_ui_->actionCaptureOptions->setIcon(StockIcon("x-capture-options"));
|
|
|
|
// Menu icons are disabled in main_window.ui for these items.
|
|
main_ui_->actionFileOpen->setIcon(StockIcon("document-open"));
|
|
main_ui_->actionFileSave->setIcon(StockIcon("x-capture-file-save"));
|
|
main_ui_->actionFileClose->setIcon(StockIcon("x-capture-file-close"));
|
|
main_ui_->actionViewReload->setIcon(StockIcon("x-capture-file-reload"));
|
|
|
|
main_ui_->actionEditFindPacket->setIcon(StockIcon("edit-find"));
|
|
main_ui_->actionGoPreviousPacket->setIcon(StockIcon("go-previous"));
|
|
main_ui_->actionGoNextPacket->setIcon(StockIcon("go-next"));
|
|
main_ui_->actionGoGoToPacket->setIcon(StockIcon("go-jump"));
|
|
main_ui_->actionGoFirstPacket->setIcon(StockIcon("go-first"));
|
|
main_ui_->actionGoLastPacket->setIcon(StockIcon("go-last"));
|
|
main_ui_->actionGoPreviousConversationPacket->setIcon(StockIcon("go-previous"));
|
|
main_ui_->actionGoNextConversationPacket->setIcon(StockIcon("go-next"));
|
|
#if defined(Q_OS_MAC)
|
|
main_ui_->actionGoPreviousConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Comma));
|
|
main_ui_->actionGoNextConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Period));
|
|
#endif
|
|
main_ui_->actionGoPreviousHistoryPacket->setIcon(StockIcon("go-previous"));
|
|
main_ui_->actionGoNextHistoryPacket->setIcon(StockIcon("go-next"));
|
|
main_ui_->actionGoAutoScroll->setIcon(StockIcon("x-stay-last"));
|
|
|
|
main_ui_->actionViewColorizePacketList->setIcon(StockIcon("x-colorize-packets"));
|
|
|
|
QList<QKeySequence> zi_seq = main_ui_->actionViewZoomIn->shortcuts();
|
|
zi_seq << QKeySequence(Qt::CTRL + Qt::Key_Equal);
|
|
main_ui_->actionViewZoomIn->setIcon(StockIcon("zoom-in"));
|
|
main_ui_->actionViewZoomIn->setShortcuts(zi_seq);
|
|
main_ui_->actionViewZoomOut->setIcon(StockIcon("zoom-out"));
|
|
main_ui_->actionViewNormalSize->setIcon(StockIcon("zoom-original"));
|
|
main_ui_->actionViewResizeColumns->setIcon(StockIcon("x-resize-columns"));
|
|
|
|
main_ui_->actionNewDisplayFilterExpression->setIcon(StockIcon("list-add"));
|
|
}
|
|
|
|
void MainWindow::initShowHideMainWidgets()
|
|
{
|
|
if (show_hide_actions_) {
|
|
return;
|
|
}
|
|
|
|
show_hide_actions_ = new QActionGroup(this);
|
|
QMap<QAction *, QWidget *> shmw_actions;
|
|
|
|
show_hide_actions_->setExclusive(false);
|
|
shmw_actions[main_ui_->actionViewMainToolbar] = main_ui_->mainToolBar;
|
|
shmw_actions[main_ui_->actionViewFilterToolbar] = main_ui_->displayFilterToolBar;
|
|
#if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
|
|
shmw_actions[main_ui_->actionViewWirelessToolbar] = main_ui_->wirelessToolBar;
|
|
#endif
|
|
shmw_actions[main_ui_->actionViewStatusBar] = main_ui_->statusBar;
|
|
shmw_actions[main_ui_->actionViewPacketList] = packet_list_;
|
|
shmw_actions[main_ui_->actionViewPacketDetails] = proto_tree_;
|
|
shmw_actions[main_ui_->actionViewPacketBytes] = byte_view_tab_;
|
|
shmw_actions[main_ui_->actionViewPacketDiagram] = packet_diagram_;
|
|
|
|
foreach(QAction *shmwa, shmw_actions.keys()) {
|
|
shmwa->setData(QVariant::fromValue(shmw_actions[shmwa]));
|
|
show_hide_actions_->addAction(shmwa);
|
|
}
|
|
|
|
// Initial hide the Interface Toolbar submenu
|
|
main_ui_->menuInterfaceToolbars->menuAction()->setVisible(false);
|
|
|
|
/* Initially hide the additional toolbars menus */
|
|
main_ui_->menuAdditionalToolbars->menuAction()->setVisible(false);
|
|
|
|
connect(show_hide_actions_, SIGNAL(triggered(QAction*)), this, SLOT(showHideMainWidgets(QAction*)));
|
|
}
|
|
|
|
void MainWindow::initTimeDisplayFormatMenu()
|
|
{
|
|
if (time_display_actions_) {
|
|
return;
|
|
}
|
|
|
|
time_display_actions_ = new QActionGroup(this);
|
|
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatDateYMDandTimeOfDay] = TS_ABSOLUTE_WITH_YMD;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatDateYDOYandTimeOfDay] = TS_ABSOLUTE_WITH_YDOY;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatTimeOfDay] = TS_ABSOLUTE;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceEpoch] = TS_EPOCH;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceBeginningOfCapture] = TS_RELATIVE;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousCapturedPacket] = TS_DELTA;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousDisplayedPacket] = TS_DELTA_DIS;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYMDandTimeOfDay] = TS_UTC_WITH_YMD;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYDOYandTimeOfDay] = TS_UTC_WITH_YDOY;
|
|
td_actions[main_ui_->actionViewTimeDisplayFormatUTCTimeOfDay] = TS_UTC;
|
|
|
|
foreach(QAction* tda, td_actions.keys()) {
|
|
tda->setData(QVariant::fromValue(td_actions[tda]));
|
|
time_display_actions_->addAction(tda);
|
|
}
|
|
|
|
connect(time_display_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampFormat(QAction*)));
|
|
}
|
|
|
|
void MainWindow::initTimePrecisionFormatMenu()
|
|
{
|
|
if (time_precision_actions_) {
|
|
return;
|
|
}
|
|
|
|
time_precision_actions_ = new QActionGroup(this);
|
|
|
|
tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionAutomatic] = TS_PREC_AUTO;
|
|
tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionSeconds] = TS_PREC_FIXED_SEC;
|
|
tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionDeciseconds] = TS_PREC_FIXED_DSEC;
|
|
tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionCentiseconds] = TS_PREC_FIXED_CSEC;
|
|
tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMilliseconds] = TS_PREC_FIXED_MSEC;
|
|
tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMicroseconds] = TS_PREC_FIXED_USEC;
|
|
tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionNanoseconds] = TS_PREC_FIXED_NSEC;
|
|
|
|
foreach(QAction* tpa, tp_actions.keys()) {
|
|
tpa->setData(QVariant::fromValue(tp_actions[tpa]));
|
|
time_precision_actions_->addAction(tpa);
|
|
}
|
|
|
|
connect(time_precision_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampPrecision(QAction*)));
|
|
}
|
|
|
|
// Menu items which will be disabled when we freeze() and whose state will
|
|
// be restored when we thaw(). Add to the list as needed.
|
|
void MainWindow::initFreezeActions()
|
|
{
|
|
QList<QAction *> freeze_actions = QList<QAction *>()
|
|
<< main_ui_->actionFileClose
|
|
<< main_ui_->actionViewReload
|
|
<< main_ui_->actionEditMarkPacket
|
|
<< main_ui_->actionEditMarkAllDisplayed
|
|
<< main_ui_->actionEditUnmarkAllDisplayed
|
|
<< main_ui_->actionEditIgnorePacket
|
|
<< main_ui_->actionEditIgnoreAllDisplayed
|
|
<< main_ui_->actionEditUnignoreAllDisplayed
|
|
<< main_ui_->actionEditSetTimeReference
|
|
<< main_ui_->actionEditUnsetAllTimeReferences;
|
|
|
|
foreach(QAction *action, freeze_actions) {
|
|
freeze_actions_ << QPair<QAction *, bool>(action, false);
|
|
}
|
|
}
|
|
|
|
void MainWindow::initConversationMenus()
|
|
{
|
|
int i;
|
|
|
|
QList<QAction *> cc_actions = QList<QAction *>()
|
|
<< main_ui_->actionViewColorizeConversation1 << main_ui_->actionViewColorizeConversation2
|
|
<< main_ui_->actionViewColorizeConversation3 << main_ui_->actionViewColorizeConversation4
|
|
<< main_ui_->actionViewColorizeConversation5 << main_ui_->actionViewColorizeConversation6
|
|
<< main_ui_->actionViewColorizeConversation7 << main_ui_->actionViewColorizeConversation8
|
|
<< main_ui_->actionViewColorizeConversation9 << main_ui_->actionViewColorizeConversation10;
|
|
|
|
for (GList *conv_filter_list_entry = conv_filter_list; conv_filter_list_entry; conv_filter_list_entry = gxx_list_next(conv_filter_list_entry)) {
|
|
// Main menu items
|
|
conversation_filter_t* conv_filter = gxx_list_data(conversation_filter_t *, conv_filter_list_entry);
|
|
ConversationAction *conv_action = new ConversationAction(main_ui_->menuConversationFilter, conv_filter);
|
|
main_ui_->menuConversationFilter->addAction(conv_action);
|
|
|
|
connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*)));
|
|
connect(conv_action, SIGNAL(triggered()), this, SLOT(applyConversationFilter()));
|
|
|
|
// Packet list context menu items
|
|
packet_list_->conversationMenu()->addAction(conv_action);
|
|
|
|
QMenu *submenu = packet_list_->colorizeMenu()->addMenu(conv_action->text());
|
|
i = 1;
|
|
|
|
foreach(QAction *cc_action, cc_actions) {
|
|
conv_action = new ConversationAction(submenu, conv_filter);
|
|
conv_action->setText(cc_action->text());
|
|
conv_action->setIcon(cc_action->icon());
|
|
conv_action->setColorNumber(i++);
|
|
submenu->addAction(conv_action);
|
|
connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*)));
|
|
connect(conv_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered()));
|
|
}
|
|
|
|
conv_action = new ConversationAction(submenu, conv_filter);
|
|
conv_action->setText(main_ui_->actionViewColorizeNewColoringRule->text());
|
|
submenu->addAction(conv_action);
|
|
connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*)));
|
|
connect(conv_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered()));
|
|
|
|
// Proto tree conversation menu is filled in in ProtoTree::contextMenuEvent.
|
|
// We should probably do that here.
|
|
}
|
|
|
|
// Proto tree colorization items
|
|
i = 1;
|
|
ColorizeAction *colorize_action;
|
|
foreach(QAction *cc_action, cc_actions) {
|
|
colorize_action = new ColorizeAction(proto_tree_->colorizeMenu());
|
|
colorize_action->setText(cc_action->text());
|
|
colorize_action->setIcon(cc_action->icon());
|
|
colorize_action->setColorNumber(i++);
|
|
proto_tree_->colorizeMenu()->addAction(colorize_action);
|
|
connect(this, SIGNAL(fieldFilterChanged(QByteArray)), colorize_action, SLOT(setFieldFilter(QByteArray)));
|
|
connect(colorize_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered()));
|
|
}
|
|
|
|
colorize_action = new ColorizeAction(proto_tree_->colorizeMenu());
|
|
colorize_action->setText(main_ui_->actionViewColorizeNewColoringRule->text());
|
|
proto_tree_->colorizeMenu()->addAction(colorize_action);
|
|
connect(this, SIGNAL(fieldFilterChanged(QByteArray)), colorize_action, SLOT(setFieldFilter(QByteArray)));
|
|
connect(colorize_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered()));
|
|
}
|
|
|
|
gboolean MainWindow::addExportObjectsMenuItem(const void *, void *value, void *userdata)
|
|
{
|
|
register_eo_t *eo = (register_eo_t*)value;
|
|
MainWindow *window = (MainWindow*)userdata;
|
|
|
|
ExportObjectAction *export_action = new ExportObjectAction(window->main_ui_->menuFileExportObjects, eo);
|
|
window->main_ui_->menuFileExportObjects->addAction(export_action);
|
|
|
|
//initially disable until a file is loaded (then file signals will take over)
|
|
export_action->setEnabled(false);
|
|
|
|
connect(&window->capture_file_, SIGNAL(captureEvent(CaptureEvent)), export_action, SLOT(captureFileEvent(CaptureEvent)));
|
|
connect(export_action, SIGNAL(triggered()), window, SLOT(applyExportObject()));
|
|
return FALSE;
|
|
}
|
|
|
|
void MainWindow::initExportObjectsMenus()
|
|
{
|
|
eo_iterate_tables(addExportObjectsMenuItem, this);
|
|
}
|
|
|
|
// Titlebar
|
|
void MainWindow::setTitlebarForCaptureFile()
|
|
{
|
|
if (capture_file_.capFile() && capture_file_.capFile()->filename) {
|
|
setWSWindowTitle(QString("[*]%1").arg(capture_file_.fileDisplayName()));
|
|
//
|
|
// XXX - on non-Mac platforms, put in the application
|
|
// name? Or do so only for temporary files?
|
|
//
|
|
if (!capture_file_.capFile()->is_tempfile) {
|
|
//
|
|
// Set the file path; that way, for macOS, it'll set the
|
|
// "proxy icon".
|
|
//
|
|
setWindowFilePath(capture_file_.filePath());
|
|
}
|
|
setWindowModified(cf_has_unsaved_data(capture_file_.capFile()));
|
|
} else {
|
|
/* We have no capture file. */
|
|
setWSWindowTitle();
|
|
}
|
|
}
|
|
|
|
QString MainWindow::replaceWindowTitleVariables(QString title)
|
|
{
|
|
title.replace("%P", get_profile_name());
|
|
title.replace("%V", get_ws_vcs_version_info());
|
|
|
|
if (title.contains("%F")) {
|
|
// %F is file path of the capture file.
|
|
if (capture_file_.capFile()) {
|
|
// get_dirname() will overwrite the argument so make a copy first
|
|
char *filename = g_strdup(capture_file_.capFile()->filename);
|
|
QString file(get_dirname(filename));
|
|
g_free(filename);
|
|
#ifndef _WIN32
|
|
// Substitute HOME with ~
|
|
QString homedir(g_getenv("HOME"));
|
|
if (!homedir.isEmpty()) {
|
|
homedir.remove(QRegularExpression("[/]+$"));
|
|
file.replace(homedir, "~");
|
|
}
|
|
#endif
|
|
title.replace("%F", file);
|
|
} else {
|
|
// No file loaded, no folder name
|
|
title.remove("%F");
|
|
}
|
|
}
|
|
|
|
if (title.contains("%S")) {
|
|
// %S is a conditional separator (" - ") that only shows when surrounded by variables
|
|
// with values or static text. Remove repeating, leading and trailing separators.
|
|
title.replace(QRegularExpression("(%S)+"), "%S");
|
|
title.remove(QRegularExpression("^%S|%S$"));
|
|
#ifdef __APPLE__
|
|
// On macOS we separate with a unicode em dash
|
|
title.replace("%S", " " UTF8_EM_DASH " ");
|
|
#else
|
|
title.replace("%S", " - ");
|
|
#endif
|
|
}
|
|
|
|
return title;
|
|
}
|
|
|
|
void MainWindow::setWSWindowTitle(QString title)
|
|
{
|
|
if (title.isEmpty()) {
|
|
title = tr("The Wireshark Network Analyzer");
|
|
}
|
|
|
|
if (prefs.gui_prepend_window_title && prefs.gui_prepend_window_title[0]) {
|
|
QString custom_title = replaceWindowTitleVariables(prefs.gui_prepend_window_title);
|
|
if (custom_title.length() > 0) {
|
|
title.prepend(QString("[%1] ").arg(custom_title));
|
|
}
|
|
}
|
|
|
|
if (prefs.gui_window_title && prefs.gui_window_title[0]) {
|
|
QString custom_title = replaceWindowTitleVariables(prefs.gui_window_title);
|
|
if (custom_title.length() > 0) {
|
|
#ifdef __APPLE__
|
|
// On macOS we separate the titles with a unicode em dash
|
|
title.append(QString(" %1 %2").arg(UTF8_EM_DASH).arg(custom_title));
|
|
#else
|
|
title.append(QString(" [%1]").arg(custom_title));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
setWindowTitle(title);
|
|
setWindowFilePath(NULL);
|
|
}
|
|
|
|
void MainWindow::setTitlebarForCaptureInProgress()
|
|
{
|
|
if (capture_file_.capFile()) {
|
|
setWSWindowTitle(tr("Capturing from %1").arg(cf_get_tempfile_source(capture_file_.capFile())));
|
|
} else {
|
|
/* We have no capture in progress. */
|
|
setWSWindowTitle();
|
|
}
|
|
}
|
|
|
|
// Menu state
|
|
|
|
/* Enable or disable menu items based on whether you have a capture file
|
|
you've finished reading and, if you have one, whether it's been saved
|
|
and whether it could be saved except by copying the raw packet data. */
|
|
void MainWindow::setMenusForCaptureFile(bool force_disable)
|
|
{
|
|
bool enable = true;
|
|
bool can_write = false;
|
|
bool can_save = false;
|
|
bool can_save_as = false;
|
|
|
|
if (force_disable || capture_file_.capFile() == NULL || capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
|
|
/* We have no capture file or we're currently reading a file */
|
|
enable = false;
|
|
} else {
|
|
/* We have a capture file. Can we write or save? */
|
|
can_write = cf_can_write_with_wiretap(capture_file_.capFile());
|
|
can_save = cf_can_save(capture_file_.capFile());
|
|
can_save_as = cf_can_save_as(capture_file_.capFile());
|
|
}
|
|
|
|
main_ui_->actionViewReload_as_File_Format_or_Capture->setEnabled(enable);
|
|
main_ui_->actionFileMerge->setEnabled(can_write);
|
|
main_ui_->actionFileClose->setEnabled(enable);
|
|
main_ui_->actionFileSave->setEnabled(can_save);
|
|
main_ui_->actionFileSaveAs->setEnabled(can_save_as);
|
|
main_ui_->actionStatisticsCaptureFileProperties->setEnabled(enable);
|
|
/*
|
|
* "Export Specified Packets..." should be available only if
|
|
* we can write the file out in at least one format.
|
|
*/
|
|
main_ui_->actionFileExportPackets->setEnabled(can_write);
|
|
|
|
main_ui_->actionFileExportAsCArrays->setEnabled(enable);
|
|
main_ui_->actionFileExportAsCSV->setEnabled(enable);
|
|
main_ui_->actionFileExportAsPDML->setEnabled(enable);
|
|
main_ui_->actionFileExportAsPlainText->setEnabled(enable);
|
|
main_ui_->actionFileExportAsPSML->setEnabled(enable);
|
|
main_ui_->actionFileExportAsJSON->setEnabled(enable);
|
|
|
|
main_ui_->actionFileExportPacketBytes->setEnabled(enable);
|
|
main_ui_->actionFileExportPDU->setEnabled(enable);
|
|
main_ui_->actionFileExportTLSSessionKeys->setEnabled(enable);
|
|
|
|
foreach(QAction *eo_action, main_ui_->menuFileExportObjects->actions()) {
|
|
eo_action->setEnabled(enable);
|
|
}
|
|
|
|
main_ui_->actionViewReload->setEnabled(enable);
|
|
|
|
#ifdef HAVE_SOFTWARE_UPDATE
|
|
// We might want to enable or disable automatic checks here as well.
|
|
update_action_->setEnabled(!can_save);
|
|
#endif
|
|
}
|
|
|
|
void MainWindow::setMenusForCaptureInProgress(bool capture_in_progress) {
|
|
/* Either a capture was started or stopped; in either case, it's not
|
|
in the process of stopping, so allow quitting. */
|
|
|
|
main_ui_->actionFileOpen->setEnabled(!capture_in_progress);
|
|
main_ui_->menuOpenRecentCaptureFile->setEnabled(!capture_in_progress);
|
|
|
|
main_ui_->actionFileExportAsCArrays->setEnabled(capture_in_progress);
|
|
main_ui_->actionFileExportAsCSV->setEnabled(capture_in_progress);
|
|
main_ui_->actionFileExportAsPDML->setEnabled(capture_in_progress);
|
|
main_ui_->actionFileExportAsPlainText->setEnabled(capture_in_progress);
|
|
main_ui_->actionFileExportAsPSML->setEnabled(capture_in_progress);
|
|
main_ui_->actionFileExportAsJSON->setEnabled(capture_in_progress);
|
|
|
|
main_ui_->actionFileExportPacketBytes->setEnabled(capture_in_progress);
|
|
main_ui_->actionFileExportPDU->setEnabled(!capture_in_progress);
|
|
main_ui_->actionFileExportTLSSessionKeys->setEnabled(capture_in_progress);
|
|
|
|
foreach(QAction *eo_action, main_ui_->menuFileExportObjects->actions()) {
|
|
eo_action->setEnabled(capture_in_progress);
|
|
}
|
|
|
|
main_ui_->menuFileSet->setEnabled(!capture_in_progress);
|
|
main_ui_->actionFileQuit->setEnabled(true);
|
|
#ifdef HAVE_SOFTWARE_UPDATE
|
|
// We might want to enable or disable automatic checks here as well.
|
|
update_action_->setEnabled(!capture_in_progress);
|
|
#endif
|
|
|
|
main_ui_->actionStatisticsCaptureFileProperties->setEnabled(capture_in_progress);
|
|
|
|
// XXX Fix packet list heading menu sensitivity
|
|
// set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortAscending",
|
|
// !capture_in_progress);
|
|
// set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortDescending",
|
|
// !capture_in_progress);
|
|
// set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/NoSorting",
|
|
// !capture_in_progress);
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
main_ui_->actionCaptureOptions->setEnabled(!capture_in_progress);
|
|
main_ui_->actionCaptureStart->setEnabled(!capture_in_progress);
|
|
main_ui_->actionCaptureStart->setChecked(capture_in_progress);
|
|
main_ui_->actionCaptureStop->setEnabled(capture_in_progress);
|
|
main_ui_->actionCaptureRestart->setEnabled(capture_in_progress);
|
|
main_ui_->actionCaptureRefreshInterfaces->setEnabled(!capture_in_progress);
|
|
#endif /* HAVE_LIBPCAP */
|
|
|
|
}
|
|
|
|
void MainWindow::setMenusForCaptureStopping() {
|
|
main_ui_->actionFileQuit->setEnabled(false);
|
|
#ifdef HAVE_SOFTWARE_UPDATE
|
|
update_action_->setEnabled(false);
|
|
#endif
|
|
main_ui_->actionStatisticsCaptureFileProperties->setEnabled(false);
|
|
#ifdef HAVE_LIBPCAP
|
|
main_ui_->actionCaptureStart->setChecked(false);
|
|
main_ui_->actionCaptureStop->setEnabled(false);
|
|
main_ui_->actionCaptureRestart->setEnabled(false);
|
|
#endif /* HAVE_LIBPCAP */
|
|
}
|
|
|
|
void MainWindow::setForCapturedPackets(bool have_captured_packets)
|
|
{
|
|
main_ui_->actionFilePrint->setEnabled(have_captured_packets);
|
|
|
|
// set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/Print",
|
|
// have_captured_packets);
|
|
|
|
main_ui_->actionEditFindPacket->setEnabled(have_captured_packets);
|
|
main_ui_->actionEditFindNext->setEnabled(have_captured_packets);
|
|
main_ui_->actionEditFindPrevious->setEnabled(have_captured_packets);
|
|
|
|
main_ui_->actionGoGoToPacket->setEnabled(have_captured_packets);
|
|
main_ui_->actionGoPreviousPacket->setEnabled(have_captured_packets);
|
|
main_ui_->actionGoNextPacket->setEnabled(have_captured_packets);
|
|
main_ui_->actionGoFirstPacket->setEnabled(have_captured_packets);
|
|
main_ui_->actionGoLastPacket->setEnabled(have_captured_packets);
|
|
main_ui_->actionGoNextConversationPacket->setEnabled(have_captured_packets);
|
|
main_ui_->actionGoPreviousConversationPacket->setEnabled(have_captured_packets);
|
|
|
|
main_ui_->actionViewZoomIn->setEnabled(have_captured_packets);
|
|
main_ui_->actionViewZoomOut->setEnabled(have_captured_packets);
|
|
main_ui_->actionViewNormalSize->setEnabled(have_captured_packets);
|
|
main_ui_->actionViewResizeColumns->setEnabled(have_captured_packets);
|
|
|
|
main_ui_->actionStatisticsCaptureFileProperties->setEnabled(have_captured_packets);
|
|
main_ui_->actionStatisticsProtocolHierarchy->setEnabled(have_captured_packets);
|
|
main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets);
|
|
}
|
|
|
|
void MainWindow::setMenusForFileSet(bool enable_list_files) {
|
|
bool enable_next = fileset_get_next() != NULL && enable_list_files;
|
|
bool enable_prev = fileset_get_previous() != NULL && enable_list_files;
|
|
|
|
main_ui_->actionFileSetListFiles->setEnabled(enable_list_files);
|
|
main_ui_->actionFileSetNextFile->setEnabled(enable_next);
|
|
main_ui_->actionFileSetPreviousFile->setEnabled(enable_prev);
|
|
}
|
|
|
|
void MainWindow::setWindowIcon(const QIcon &icon) {
|
|
wsApp->setWindowIcon(icon);
|
|
QMainWindow::setWindowIcon(icon);
|
|
}
|
|
|
|
void MainWindow::updateForUnsavedChanges() {
|
|
setTitlebarForCaptureFile();
|
|
setMenusForCaptureFile();
|
|
}
|
|
|
|
void MainWindow::changeEvent(QEvent* event)
|
|
{
|
|
if (0 != event)
|
|
{
|
|
switch (event->type())
|
|
{
|
|
case QEvent::LanguageChange:
|
|
main_ui_->retranslateUi(this);
|
|
// make sure that the "Clear Menu" item is retranslated
|
|
wsApp->emitAppSignal(WiresharkApplication::RecentCapturesChanged);
|
|
break;
|
|
case QEvent::LocaleChange: {
|
|
QString locale = QLocale::system().name();
|
|
locale.truncate(locale.lastIndexOf('_'));
|
|
wsApp->loadLanguage(locale);
|
|
}
|
|
break;
|
|
case QEvent::WindowStateChange:
|
|
main_ui_->actionViewFullScreen->setChecked(this->isFullScreen());
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
QMainWindow::changeEvent(event);
|
|
}
|
|
|
|
/* Update main window items based on whether there's a capture in progress. */
|
|
void MainWindow::setForCaptureInProgress(bool capture_in_progress, bool handle_toolbars, GArray *ifaces)
|
|
{
|
|
setMenusForCaptureInProgress(capture_in_progress);
|
|
|
|
#if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
|
|
wireless_frame_->setCaptureInProgress(capture_in_progress);
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBPCAP
|
|
packet_list_->setCaptureInProgress(capture_in_progress);
|
|
packet_list_->setVerticalAutoScroll(capture_in_progress && main_ui_->actionGoAutoScroll->isChecked());
|
|
|
|
// set_capture_if_dialog_for_capture_in_progress(capture_in_progress);
|
|
#endif
|
|
|
|
if (handle_toolbars) {
|
|
QList<InterfaceToolbar *> toolbars = findChildren<InterfaceToolbar *>();
|
|
foreach(InterfaceToolbar *toolbar, toolbars) {
|
|
if (capture_in_progress) {
|
|
toolbar->startCapture(ifaces);
|
|
} else {
|
|
toolbar->stopCapture();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static QList<register_stat_group_t> menu_groups = QList<register_stat_group_t>()
|
|
<< REGISTER_ANALYZE_GROUP_UNSORTED
|
|
<< REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER
|
|
<< REGISTER_STAT_GROUP_UNSORTED
|
|
<< REGISTER_STAT_GROUP_GENERIC
|
|
<< REGISTER_STAT_GROUP_CONVERSATION_LIST
|
|
<< REGISTER_STAT_GROUP_ENDPOINT_LIST
|
|
<< REGISTER_STAT_GROUP_RESPONSE_TIME
|
|
<< REGISTER_STAT_GROUP_RSERPOOL
|
|
<< REGISTER_STAT_GROUP_TELEPHONY
|
|
<< REGISTER_STAT_GROUP_TELEPHONY_ANSI
|
|
<< REGISTER_STAT_GROUP_TELEPHONY_GSM
|
|
<< REGISTER_STAT_GROUP_TELEPHONY_LTE
|
|
<< REGISTER_STAT_GROUP_TELEPHONY_MTP3
|
|
<< REGISTER_STAT_GROUP_TELEPHONY_SCTP
|
|
<< REGISTER_TOOLS_GROUP_UNSORTED;
|
|
|
|
void MainWindow::addMenuActions(QList<QAction *> &actions, int menu_group)
|
|
{
|
|
foreach(QAction *action, actions) {
|
|
switch (menu_group) {
|
|
case REGISTER_ANALYZE_GROUP_UNSORTED:
|
|
case REGISTER_STAT_GROUP_UNSORTED:
|
|
main_ui_->menuStatistics->insertAction(
|
|
main_ui_->actionStatistics_REGISTER_STAT_GROUP_UNSORTED,
|
|
action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_RESPONSE_TIME:
|
|
main_ui_->menuServiceResponseTime->addAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_RSERPOOL:
|
|
main_ui_->menuRSerPool->addAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY:
|
|
main_ui_->menuTelephony->addAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY_ANSI:
|
|
main_ui_->menuANSI->addAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY_GSM:
|
|
main_ui_->menuGSM->addAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY_LTE:
|
|
main_ui_->menuLTE->addAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY_MTP3:
|
|
main_ui_->menuMTP3->addAction(action);
|
|
break;
|
|
case REGISTER_TOOLS_GROUP_UNSORTED:
|
|
{
|
|
// Allow the creation of submenus. Mimics the behavor of
|
|
// ui/gtk/main_menubar.c:add_menu_item_to_main_menubar
|
|
// and GtkUIManager.
|
|
//
|
|
// For now we limit the insanity to the "Tools" menu.
|
|
QStringList menu_path = action->text().split('/');
|
|
QMenu *cur_menu = main_ui_->menuTools;
|
|
while (menu_path.length() > 1) {
|
|
QString menu_title = menu_path.takeFirst();
|
|
QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
|
|
if (!submenu) {
|
|
submenu = cur_menu->addMenu(menu_title);
|
|
submenu->setObjectName(menu_title.toLower());
|
|
}
|
|
cur_menu = submenu;
|
|
}
|
|
action->setText(menu_path.last());
|
|
cur_menu->addAction(action);
|
|
break;
|
|
}
|
|
default:
|
|
// qDebug() << "FIX: Add" << action->text() << "to the menu";
|
|
break;
|
|
}
|
|
|
|
// Connect each action type to its corresponding slot. We to
|
|
// distinguish various types of actions. Setting their objectName
|
|
// seems to work OK.
|
|
if (action->objectName() == TapParameterDialog::actionName()) {
|
|
connect(action, SIGNAL(triggered(bool)), this, SLOT(openTapParameterDialog()));
|
|
} else if (action->objectName() == FunnelStatistics::actionName()) {
|
|
connect(action, SIGNAL(triggered(bool)), funnel_statistics_, SLOT(funnelActionTriggered()));
|
|
}
|
|
}
|
|
}
|
|
void MainWindow::removeMenuActions(QList<QAction *> &actions, int menu_group)
|
|
{
|
|
foreach(QAction *action, actions) {
|
|
switch (menu_group) {
|
|
case REGISTER_ANALYZE_GROUP_UNSORTED:
|
|
case REGISTER_STAT_GROUP_UNSORTED:
|
|
main_ui_->menuStatistics->removeAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_RESPONSE_TIME:
|
|
main_ui_->menuServiceResponseTime->removeAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_RSERPOOL:
|
|
main_ui_->menuRSerPool->removeAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY:
|
|
main_ui_->menuTelephony->removeAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY_ANSI:
|
|
main_ui_->menuANSI->removeAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY_GSM:
|
|
main_ui_->menuGSM->removeAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY_LTE:
|
|
main_ui_->menuLTE->removeAction(action);
|
|
break;
|
|
case REGISTER_STAT_GROUP_TELEPHONY_MTP3:
|
|
main_ui_->menuMTP3->removeAction(action);
|
|
break;
|
|
case REGISTER_TOOLS_GROUP_UNSORTED:
|
|
{
|
|
// Allow removal of submenus.
|
|
// For now we limit the insanity to the "Tools" menu.
|
|
QStringList menu_path = action->text().split('/');
|
|
QMenu *cur_menu = main_ui_->menuTools;
|
|
while (menu_path.length() > 1) {
|
|
QString menu_title = menu_path.takeFirst();
|
|
QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
|
|
cur_menu = submenu;
|
|
}
|
|
cur_menu->removeAction(action);
|
|
break;
|
|
}
|
|
default:
|
|
// qDebug() << "FIX: Remove" << action->text() << "from the menu";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MainWindow::addDynamicMenus()
|
|
{
|
|
// Manual additions
|
|
wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_GSM, main_ui_->actionTelephonyGsmMapSummary);
|
|
wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteMacStatistics);
|
|
wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteRlcStatistics);
|
|
wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteRlcGraph);
|
|
wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_MTP3, main_ui_->actionTelephonyMtp3Summary);
|
|
wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY, main_ui_->actionTelephonySipFlows);
|
|
|
|
// Fill in each menu
|
|
foreach(register_stat_group_t menu_group, menu_groups) {
|
|
QList<QAction *>actions = wsApp->dynamicMenuGroupItems(menu_group);
|
|
addMenuActions(actions, menu_group);
|
|
}
|
|
|
|
// Empty menus don't show up: https://bugreports.qt.io/browse/QTBUG-33728
|
|
// We've added a placeholder in order to make sure some menus are visible.
|
|
// Hide them as needed.
|
|
if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_ANSI).length() > 0) {
|
|
main_ui_->actionTelephonyANSIPlaceholder->setVisible(false);
|
|
}
|
|
if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_GSM).length() > 0) {
|
|
main_ui_->actionTelephonyGSMPlaceholder->setVisible(false);
|
|
}
|
|
if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_LTE).length() > 0) {
|
|
main_ui_->actionTelephonyLTEPlaceholder->setVisible(false);
|
|
}
|
|
if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_MTP3).length() > 0) {
|
|
main_ui_->actionTelephonyMTP3Placeholder->setVisible(false);
|
|
}
|
|
}
|
|
|
|
void MainWindow::reloadDynamicMenus()
|
|
{
|
|
foreach(register_stat_group_t menu_group, menu_groups) {
|
|
QList<QAction *>actions = wsApp->removedMenuGroupItems(menu_group);
|
|
removeMenuActions(actions, menu_group);
|
|
|
|
actions = wsApp->addedMenuGroupItems(menu_group);
|
|
addMenuActions(actions, menu_group);
|
|
}
|
|
|
|
wsApp->clearAddedMenuGroupItems();
|
|
wsApp->clearRemovedMenuGroupItems();
|
|
}
|
|
|
|
void MainWindow::externalMenuHelper(ext_menu_t * menu, QMenu * subMenu, gint depth)
|
|
{
|
|
QAction * itemAction = Q_NULLPTR;
|
|
ext_menubar_t * item = Q_NULLPTR;
|
|
GList * children = Q_NULLPTR;
|
|
|
|
/* There must exists an xpath parent */
|
|
Q_ASSERT(subMenu != NULL);
|
|
|
|
/* If the depth counter exceeds, something must have gone wrong */
|
|
Q_ASSERT(depth < EXT_MENUBAR_MAX_DEPTH);
|
|
|
|
children = menu->children;
|
|
/* Iterate the child entries */
|
|
while (children && children->data) {
|
|
item = gxx_list_data(ext_menubar_t *, children);
|
|
|
|
if (item->type == EXT_MENUBAR_MENU) {
|
|
/* Handle Submenu entry */
|
|
this->externalMenuHelper(item, subMenu->addMenu(item->label), depth++);
|
|
} else if (item->type == EXT_MENUBAR_SEPARATOR) {
|
|
subMenu->addSeparator();
|
|
} else if (item->type == EXT_MENUBAR_ITEM || item->type == EXT_MENUBAR_URL) {
|
|
itemAction = subMenu->addAction(item->name);
|
|
itemAction->setData(QVariant::fromValue(static_cast<void *>(item)));
|
|
itemAction->setText(item->label);
|
|
connect(itemAction, SIGNAL(triggered()),
|
|
this, SLOT(externalMenuItem_triggered()));
|
|
}
|
|
|
|
/* Iterate Loop */
|
|
children = gxx_list_next(children);
|
|
}
|
|
}
|
|
|
|
QMenu * MainWindow::searchSubMenu(QString objectName)
|
|
{
|
|
QList<QMenu*> lst;
|
|
|
|
if (objectName.length() > 0) {
|
|
QString searchName = QString("menu") + objectName;
|
|
|
|
lst = main_ui_->menuBar->findChildren<QMenu*>();
|
|
foreach(QMenu* m, lst) {
|
|
if (QString::compare(m->objectName(), searchName) == 0)
|
|
return m;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void MainWindow::addPluginIFStructures()
|
|
{
|
|
GList *user_menu = ext_menubar_get_entries();
|
|
|
|
while (user_menu && user_menu->data) {
|
|
QMenu *subMenu = Q_NULLPTR;
|
|
ext_menu_t *menu = gxx_list_data(ext_menu_t *, user_menu);
|
|
|
|
/* On this level only menu items should exist. Not doing an assert here,
|
|
* as it could be an honest mistake */
|
|
if (menu->type != EXT_MENUBAR_MENU) {
|
|
user_menu = gxx_list_next(user_menu);
|
|
continue;
|
|
}
|
|
|
|
/* Create main submenu and add it to the menubar */
|
|
if (menu->parent_menu) {
|
|
QMenu *sortUnderneath = searchSubMenu(QString(menu->parent_menu));
|
|
if (sortUnderneath)
|
|
subMenu = sortUnderneath->addMenu(menu->label);
|
|
}
|
|
|
|
if (!subMenu)
|
|
subMenu = main_ui_->menuBar->addMenu(menu->label);
|
|
|
|
/* This will generate the action structure for each menu. It is recursive,
|
|
* therefore a sub-routine, and we have a depth counter to prevent endless loops. */
|
|
this->externalMenuHelper(menu, subMenu, 0);
|
|
|
|
/* Iterate Loop */
|
|
user_menu = gxx_list_next(user_menu);
|
|
}
|
|
|
|
int cntToolbars = 0;
|
|
|
|
QMenu *tbMenu = main_ui_->menuAdditionalToolbars;
|
|
GList *if_toolbars = ext_toolbar_get_entries();
|
|
while (if_toolbars && if_toolbars->data) {
|
|
ext_toolbar_t *toolbar = gxx_list_data(ext_toolbar_t*, if_toolbars);
|
|
|
|
if (toolbar->type != EXT_TOOLBAR_BAR) {
|
|
if_toolbars = gxx_list_next(if_toolbars);
|
|
continue;
|
|
}
|
|
|
|
bool visible = g_list_find_custom(recent.gui_additional_toolbars, toolbar->name, reinterpret_cast<GCompareFunc>(strcmp)) ? true : false;
|
|
|
|
AdditionalToolBar *ifToolBar = AdditionalToolBar::create(this, toolbar);
|
|
|
|
if (ifToolBar) {
|
|
ifToolBar->setVisible(visible);
|
|
|
|
QAction *iftbAction = new QAction(QString(toolbar->name), this);
|
|
iftbAction->setToolTip(toolbar->tooltip);
|
|
iftbAction->setEnabled(true);
|
|
iftbAction->setCheckable(true);
|
|
iftbAction->setChecked(visible);
|
|
iftbAction->setToolTip(tr("Show or hide the toolbar"));
|
|
iftbAction->setData(VariantPointer<ext_toolbar_t>::asQVariant(toolbar));
|
|
|
|
QAction *before = Q_NULLPTR;
|
|
|
|
foreach(QAction *action, tbMenu->actions()) {
|
|
/* Ensure we add the menu entries in sorted order */
|
|
if (action->text().compare(toolbar->name, Qt::CaseInsensitive) > 0) {
|
|
before = action;
|
|
break;
|
|
}
|
|
}
|
|
|
|
tbMenu->insertAction(before, iftbAction);
|
|
|
|
addToolBar(Qt::TopToolBarArea, ifToolBar);
|
|
insertToolBarBreak(ifToolBar);
|
|
|
|
if (show_hide_actions_)
|
|
show_hide_actions_->addAction(iftbAction);
|
|
|
|
cntToolbars++;
|
|
}
|
|
|
|
if_toolbars = gxx_list_next(if_toolbars);
|
|
}
|
|
|
|
if (cntToolbars)
|
|
tbMenu->menuAction()->setVisible(true);
|
|
}
|
|
|
|
void MainWindow::removeAdditionalToolbar(QString toolbarName)
|
|
{
|
|
if (toolbarName.length() == 0)
|
|
return;
|
|
|
|
QList<QToolBar *> toolbars = findChildren<QToolBar *>();
|
|
foreach(QToolBar *tb, toolbars) {
|
|
AdditionalToolBar *ifToolBar = dynamic_cast<AdditionalToolBar *>(tb);
|
|
|
|
if (ifToolBar && ifToolBar->menuName().compare(toolbarName)) {
|
|
GList *entry = g_list_find_custom(recent.gui_additional_toolbars, qUtf8Printable(ifToolBar->menuName()), reinterpret_cast<GCompareFunc>(strcmp));
|
|
if (entry) {
|
|
recent.gui_additional_toolbars = g_list_remove(recent.gui_additional_toolbars, entry->data);
|
|
}
|
|
QList<QAction *> actions = main_ui_->menuAdditionalToolbars->actions();
|
|
foreach(QAction *action, actions) {
|
|
ext_toolbar_t *item = VariantPointer<ext_toolbar_t>::asPtr(action->data());
|
|
if (item && ifToolBar->menuName().compare(item->name)) {
|
|
if (show_hide_actions_)
|
|
show_hide_actions_->removeAction(action);
|
|
main_ui_->menuAdditionalToolbars->removeAction(action);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
QString MainWindow::getMwFileName()
|
|
{
|
|
return mwFileName_;
|
|
}
|
|
|
|
void MainWindow::setMwFileName(QString fileName)
|
|
{
|
|
mwFileName_ = fileName;
|
|
return;
|
|
}
|
|
|
|
bool MainWindow::hasSelection()
|
|
{
|
|
if (packet_list_)
|
|
return packet_list_->multiSelectActive();
|
|
return false;
|
|
}
|
|
|
|
QList<int> MainWindow::selectedRows(bool useFrameNum)
|
|
{
|
|
if (packet_list_)
|
|
return packet_list_->selectedRows(useFrameNum);
|
|
return QList<int>();
|
|
}
|
|
|
|
frame_data * MainWindow::frameDataForRow(int row) const
|
|
{
|
|
if (packet_list_)
|
|
return packet_list_->getFDataForRow(row);
|
|
|
|
return Q_NULLPTR;
|
|
}
|
|
|
|
// Finds rtp id for selected stream and adds it to stream_ids
|
|
// If reverse is set, tries to find reverse stream too
|
|
// Return error string if error happens
|
|
//
|
|
// Note: Caller must free each returned rtpstream_info_t
|
|
QString MainWindow::findRtpStreams(QVector<rtpstream_id_t *> *stream_ids, bool reverse)
|
|
{
|
|
rtpstream_tapinfo_t tapinfo;
|
|
rtpstream_id_t *fwd_id, *rev_id;
|
|
bool fwd_id_used, rev_id_used;
|
|
const gchar filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)";
|
|
dfilter_t *sfcode;
|
|
gchar *err_msg;
|
|
|
|
/* Try to get the hfid for "rtp.ssrc". */
|
|
int hfid_rtp_ssrc = proto_registrar_get_id_byname("rtp.ssrc");
|
|
if (hfid_rtp_ssrc == -1) {
|
|
return tr("There is no \"rtp.ssrc\" field in this version of Wireshark.");
|
|
}
|
|
|
|
/* Try to compile the filter. */
|
|
if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
|
|
QString err = QString(err_msg);
|
|
g_free(err_msg);
|
|
return err;
|
|
}
|
|
|
|
if (!capture_file_.capFile() || !capture_file_.capFile()->current_frame) close();
|
|
|
|
if (!cf_read_current_record(capture_file_.capFile())) close();
|
|
|
|
frame_data *fdata = capture_file_.capFile()->current_frame;
|
|
|
|
epan_dissect_t edt;
|
|
|
|
epan_dissect_init(&edt, capture_file_.capFile()->epan, true, false);
|
|
epan_dissect_prime_with_dfilter(&edt, sfcode);
|
|
epan_dissect_prime_with_hfid(&edt, hfid_rtp_ssrc);
|
|
epan_dissect_run(&edt, capture_file_.capFile()->cd_t,
|
|
&capture_file_.capFile()->rec,
|
|
frame_tvbuff_new_buffer(
|
|
&capture_file_.capFile()->provider, fdata,
|
|
&capture_file_.capFile()->buf),
|
|
fdata, NULL);
|
|
|
|
/*
|
|
* Packet must be an RTPv2 packet with an SSRC; we use the filter to
|
|
* check.
|
|
*/
|
|
if (!dfilter_apply_edt(sfcode, &edt)) {
|
|
epan_dissect_cleanup(&edt);
|
|
dfilter_free(sfcode);
|
|
return tr("Please select an RTPv2 packet with an SSRC value");
|
|
}
|
|
|
|
dfilter_free(sfcode);
|
|
|
|
/* We need the SSRC value of the current frame; try to get it. */
|
|
GPtrArray *gp = proto_get_finfo_ptr_array(edt.tree, hfid_rtp_ssrc);
|
|
if (gp == NULL || gp->len == 0) {
|
|
/* XXX - should not happen, as the filter includes rtp.ssrc */
|
|
epan_dissect_cleanup(&edt);
|
|
return tr("SSRC value not found.");
|
|
}
|
|
|
|
/*
|
|
* OK, we have the SSRC value, so we can proceed.
|
|
* Allocate RTP stream ID structures.
|
|
*/
|
|
fwd_id = g_new0(rtpstream_id_t, 1);
|
|
fwd_id_used = false;
|
|
rev_id = g_new0(rtpstream_id_t, 1);
|
|
rev_id_used = false;
|
|
|
|
/* Get the IP and port values for the forward direction. */
|
|
rtpstream_id_copy_pinfo(&(edt.pi), fwd_id, false);
|
|
|
|
/* assume the inverse ip/port combination for the reverse direction */
|
|
rtpstream_id_copy_pinfo(&(edt.pi), rev_id, true);
|
|
|
|
/* Save the SSRC value for the forward direction. */
|
|
fwd_id->ssrc = fvalue_get_uinteger(&((field_info *)gp->pdata[0])->value);
|
|
|
|
epan_dissect_cleanup(&edt);
|
|
|
|
/* Register the tap listener */
|
|
memset(&tapinfo, 0, sizeof(rtpstream_tapinfo_t));
|
|
tapinfo.tap_data = this;
|
|
tapinfo.mode = TAP_ANALYSE;
|
|
|
|
/* Scan for RTP streams (redissect all packets) */
|
|
rtpstream_scan(&tapinfo, capture_file_.capFile(), Q_NULLPTR);
|
|
|
|
for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = gxx_list_next(strinfo_list)) {
|
|
rtpstream_info_t * strinfo = gxx_list_data(rtpstream_info_t*, strinfo_list);
|
|
if (rtpstream_id_equal(&(strinfo->id), fwd_id,RTPSTREAM_ID_EQUAL_NONE))
|
|
{
|
|
*stream_ids << fwd_id;
|
|
fwd_id_used = true;
|
|
}
|
|
|
|
if (rtpstream_id_equal(&(strinfo->id), rev_id,RTPSTREAM_ID_EQUAL_NONE))
|
|
{
|
|
if (rev_id->ssrc == 0) {
|
|
rev_id->ssrc = strinfo->id.ssrc;
|
|
}
|
|
if (reverse) {
|
|
*stream_ids << rev_id;
|
|
rev_id_used = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// XXX - is it guaranteed that fwd_id and rev_id were both added to
|
|
// *stream_ids? If so, this isn't necessary.
|
|
//
|
|
if (!fwd_id_used) {
|
|
rtpstream_id_free(fwd_id);
|
|
}
|
|
if (!rev_id_used) {
|
|
rtpstream_id_free(rev_id);
|
|
}
|
|
return NULL;
|
|
}
|
|
|