Revert "TODO epan/dissectors/asn1/kerberos/packet-kerberos-template.c new GSS flags"
[wireshark-sm.git] / ui / qt / wireshark_main_window.cpp
blob53ca0ef1b2cc4b91b04a6b3a1a7cd849bec03f34
1 /* main_window.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
10 #include "main_application.h"
11 #include "wireshark_main_window.h"
14 * The generated Ui_WiresharkMainWindow::setupUi() can grow larger than our configured limit,
15 * so turn off -Wframe-larger-than= for ui_wireshark_main_window.h.
17 DIAG_OFF(frame-larger-than=)
18 #include <ui_wireshark_main_window.h>
19 DIAG_ON(frame-larger-than=)
21 #include <epan/addr_resolv.h>
22 #include "epan/conversation_filter.h"
23 #include <epan/epan_dissect.h>
24 #include <wsutil/filesystem.h>
25 #include <wsutil/wslog.h>
26 #include <wsutil/ws_assert.h>
27 #include <wsutil/version_info.h>
28 #include <epan/prefs.h>
29 #include <epan/stats_tree_priv.h>
30 #include <epan/plugin_if.h>
31 #include <epan/export_object.h>
32 #include <frame_tvbuff.h>
34 #include "ui/iface_toolbar.h"
35 #include "ui/commandline.h"
37 #ifdef HAVE_LIBPCAP
38 #include "ui/capture.h"
39 #include <capture/capture_session.h>
40 #endif
42 #include "ui/alert_box.h"
43 #ifdef HAVE_LIBPCAP
44 #include "ui/capture_ui_utils.h"
45 #endif
46 #include "ui/capture_globals.h"
47 #include "ui/main_statusbar.h"
48 #include "ui/recent.h"
49 #include "ui/recent_utils.h"
50 #include "ui/util.h"
51 #include "ui/preference_utils.h"
53 #include "byte_view_tab.h"
54 #ifdef HAVE_LIBPCAP
55 #include "capture_options_dialog.h"
56 #endif
57 #include "conversation_colorize_action.h"
58 #include "export_dissection_dialog.h"
59 #include "export_object_action.h"
60 #include "file_set_dialog.h"
61 #include "filter_dialog.h"
62 #include "follow_stream_action.h"
63 #include "funnel_statistics.h"
64 #include "import_text_dialog.h"
65 #include "interface_toolbar.h"
66 #include "packet_diagram.h"
67 #include "packet_list.h"
68 #include "proto_tree.h"
69 #include "simple_dialog.h"
70 #include "tap_parameter_dialog.h"
71 #include "wireless_frame.h"
72 #include <ui/qt/widgets/wireless_timeline.h>
74 #include <ui/qt/widgets/additional_toolbar.h>
75 #include <ui/qt/widgets/display_filter_edit.h>
76 #include <ui/qt/widgets/filter_expression_toolbar.h>
78 #include <ui/qt/utils/color_utils.h>
79 #include <ui/qt/utils/profile_switcher.h>
80 #include <ui/qt/utils/qt_ui_utils.h>
81 #include <ui/qt/utils/stock_icon.h>
82 #include <ui/qt/utils/variant_pointer.h>
84 #include <QAction>
85 #include <QActionGroup>
86 #include <QIntValidator>
87 #include <QKeyEvent>
88 #include <QList>
89 #include <QMessageBox>
90 #include <QMetaObject>
91 #include <QMimeData>
92 #include <QTabWidget>
93 #include <QTextCodec>
94 #include <QToolButton>
95 #include <QTreeWidget>
96 #include <QUrl>
98 //menu_recent_file_write_all
100 // If we ever add support for multiple windows this will need to be replaced.
101 static WiresharkMainWindow *gbl_cur_main_window_;
103 static void plugin_if_mainwindow_apply_filter(GHashTable * data_set)
105 if (!gbl_cur_main_window_ || !data_set)
106 return;
108 if (g_hash_table_lookup_extended(data_set, "filter_string", NULL, NULL)) {
109 QString filter((const char *)g_hash_table_lookup(data_set, "filter_string"));
110 gbl_cur_main_window_->filterPackets(filter);
114 static void plugin_if_mainwindow_preference(GHashTable * data_set)
116 if (!gbl_cur_main_window_ || !data_set)
117 return;
119 const char * module_name;
120 const char * pref_name;
121 const char * pref_value;
123 DIAG_OFF_CAST_AWAY_CONST
124 if (g_hash_table_lookup_extended(data_set, "pref_module", NULL, (void * *)&module_name) &&
125 g_hash_table_lookup_extended(data_set, "pref_key", NULL, (void * *)&pref_name) &&
126 g_hash_table_lookup_extended(data_set, "pref_value", NULL, (void * *)&pref_value))
128 unsigned int changed_flags = prefs_store_ext(module_name, pref_name, pref_value);
129 if (changed_flags) {
130 mainApp->emitAppSignal(WiresharkApplication::PacketDissectionChanged);
131 mainApp->emitAppSignal(WiresharkApplication::PreferencesChanged);
134 DIAG_ON_CAST_AWAY_CONST
137 static void plugin_if_mainwindow_gotoframe(GHashTable * data_set)
139 if (!gbl_cur_main_window_ || !data_set)
140 return;
142 void *framenr;
144 if (g_hash_table_lookup_extended(data_set, "frame_nr", NULL, &framenr)) {
145 if (GPOINTER_TO_UINT(framenr) != 0)
146 gbl_cur_main_window_->gotoFrame(GPOINTER_TO_UINT(framenr));
150 #ifdef HAVE_LIBPCAP
152 static void plugin_if_mainwindow_get_ws_info(GHashTable * data_set)
154 if (!gbl_cur_main_window_ || !data_set)
155 return;
157 ws_info_t *ws_info = NULL;
159 if (!g_hash_table_lookup_extended(data_set, "ws_info", NULL, (void**)&ws_info))
160 return;
162 CaptureFile *cfWrap = gbl_cur_main_window_->captureFile();
163 capture_file *cf = cfWrap->capFile();
165 ws_info->ws_info_supported = true;
167 /* If we have a filename attached to ws_info clear it */
168 if (ws_info->cf_filename != NULL)
170 g_free(ws_info->cf_filename);
171 ws_info->cf_filename = NULL;
174 /* Determine the true state of the capture file. We return the true state in
175 the ws_info structure and DON'T CHANGE the cf->state as we don't want to cause problems
176 with code that follows this. */
177 if (cf)
179 if (cf->filename)
181 /* As we have a cf->filename we'll use the name and the state */
182 ws_info->cf_filename = g_strdup(cf->filename);
183 ws_info->cf_state = cf->state;
185 else
187 /* When we come through here the cf->state can show FILE_READ_DONE even though the
188 file is actually closed (no filename). A better fix would be to have a
189 FILE_CLOSE_PENDING state but that involves a lot of code change elsewhere. */
190 ws_info->cf_state = FILE_CLOSED;
194 if (!ws_info->cf_filename)
196 /* We may have a filename associated with the main window so let's use it */
197 QString fileNameString = gbl_cur_main_window_->getMwFileName();
198 if (fileNameString.length())
200 QByteArray ba = fileNameString.toLatin1();
201 const char *c_file_name = ba.data();
202 ws_info->cf_filename = g_strdup(c_file_name);
206 if (cf) {
207 ws_info->cf_count = cf->count;
209 QList<int> rows = gbl_cur_main_window_->selectedRows();
210 frame_data * fdata = NULL;
211 if (rows.count() > 0)
212 fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0));
214 if (cf->state == FILE_READ_DONE && fdata) {
215 ws_info->cf_framenr = fdata->num;
216 ws_info->frame_passed_dfilter = (fdata->passed_dfilter == 1);
218 else {
219 ws_info->cf_framenr = 0;
220 ws_info->frame_passed_dfilter = false;
223 else
225 /* Initialise the other ws_info structure values */
226 ws_info->cf_count = 0;
227 ws_info->cf_framenr = 0;
228 ws_info->frame_passed_dfilter = false;
232 #endif /* HAVE_LIBPCAP */
234 static void plugin_if_mainwindow_get_frame_data(GHashTable* data_set)
236 if (!gbl_cur_main_window_ || !data_set)
237 return;
239 plugin_if_frame_data_cb extract_cb;
240 void* user_data;
241 void** ret_value_ptr;
243 if (g_hash_table_lookup_extended(data_set, "extract_cb", NULL, (void**)&extract_cb) &&
244 g_hash_table_lookup_extended(data_set, "user_data", NULL, (void**)&user_data) &&
245 g_hash_table_lookup_extended(data_set, "ret_value_ptr", NULL, (void**)&ret_value_ptr))
247 QList<int> rows = gbl_cur_main_window_->selectedRows();
248 if (rows.count() > 0) {
249 frame_data* fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0));
250 if (fdata) {
251 *ret_value_ptr = extract_cb(fdata, user_data);
257 static void plugin_if_mainwindow_get_capture_file(GHashTable* data_set)
259 if (!gbl_cur_main_window_ || !data_set)
260 return;
262 plugin_if_capture_file_cb extract_cb;
263 void* user_data;
264 void** ret_value_ptr;
266 if (g_hash_table_lookup_extended(data_set, "extract_cb", NULL, (void**)&extract_cb) &&
267 g_hash_table_lookup_extended(data_set, "user_data", NULL, (void**)&user_data) &&
268 g_hash_table_lookup_extended(data_set, "ret_value_ptr", NULL, (void**)&ret_value_ptr))
270 CaptureFile* cfWrap = gbl_cur_main_window_->captureFile();
271 capture_file* cf = cfWrap->capFile();
272 if (cf) {
273 *ret_value_ptr = extract_cb(cf, user_data);
278 static void plugin_if_mainwindow_update_toolbars(GHashTable * data_set)
280 if (!gbl_cur_main_window_ || !data_set)
281 return;
283 if (g_hash_table_lookup_extended(data_set, "toolbar_name", NULL, NULL)) {
284 QString toolbarName((const char *)g_hash_table_lookup(data_set, "toolbar_name"));
285 gbl_cur_main_window_->removeAdditionalToolbar(toolbarName);
290 static void mainwindow_add_toolbar(const iface_toolbar *toolbar_entry)
292 if (gbl_cur_main_window_ && toolbar_entry)
294 gbl_cur_main_window_->addInterfaceToolbar(toolbar_entry);
298 static void mainwindow_remove_toolbar(const char *menu_title)
300 if (gbl_cur_main_window_ && menu_title)
302 gbl_cur_main_window_->removeInterfaceToolbar(menu_title);
306 QMenu* WiresharkMainWindow::findOrAddMenu(QMenu *parent_menu, const QStringList& menu_parts) {
307 for (auto const & menu_text : menu_parts) {
308 bool found = false;
309 for (auto const & action : parent_menu->actions()) {
310 if (action->text() == menu_text.trimmed()) {
311 parent_menu = action->menu();
312 found = true;
313 break;
316 if (!found) {
317 // If we get here the menu entry was not found, add a sub menu
318 parent_menu = parent_menu->addMenu(menu_text.trimmed());
321 return parent_menu;
324 WiresharkMainWindow::WiresharkMainWindow(QWidget *parent) :
325 MainWindow(parent),
326 main_ui_(new Ui::WiresharkMainWindow),
327 previous_focus_(NULL),
328 file_set_dialog_(NULL),
329 show_hide_actions_(NULL),
330 time_display_actions_(NULL),
331 time_precision_actions_(NULL),
332 funnel_statistics_(NULL),
333 freeze_focus_(NULL),
334 was_maximized_(false),
335 capture_stopping_(false),
336 capture_filter_valid_(false),
337 use_capturing_title_(false)
338 #ifdef HAVE_LIBPCAP
339 , capture_options_dialog_(NULL)
340 , info_data_()
341 #endif
342 #if defined(Q_OS_MAC)
343 , dock_menu_(NULL)
344 #endif
346 if (!gbl_cur_main_window_) {
347 connect(mainApp, &MainApplication::openStatCommandDialog, this, &WiresharkMainWindow::openStatCommandDialog);
348 connect(mainApp, &MainApplication::openTapParameterDialog,
349 this, [=](const QString cfg_str, const QString arg, void *userdata) {openTapParameterDialog(cfg_str, arg, userdata);});
351 gbl_cur_main_window_ = this;
352 #ifdef HAVE_LIBPCAP
353 capture_input_init(&cap_session_, CaptureFile::globalCapFile());
354 #endif
356 findTextCodecs();
357 // setpUi calls QMetaObject::connectSlotsByName(this). connectSlotsByName
358 // iterates over *all* of our children, looking for matching "on_" slots.
359 // The fewer children we have at this point the better.
360 main_ui_->setupUi(this);
361 #ifdef HAVE_SOFTWARE_UPDATE
362 update_action_ = new QAction(tr("Check for Updates…"), main_ui_->menuHelp);
363 #endif
364 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
365 wireless_frame_ = new WirelessFrame(this);
366 main_ui_->wirelessToolBar->addWidget(wireless_frame_);
367 #else
368 removeToolBar(main_ui_->wirelessToolBar);
369 main_ui_->menuView->removeAction(main_ui_->actionViewWirelessToolbar);
370 #endif
372 menu_groups_ = QList<register_stat_group_t>()
373 << REGISTER_PACKET_ANALYZE_GROUP_UNSORTED
374 << REGISTER_PACKET_STAT_GROUP_UNSORTED
375 << REGISTER_STAT_GROUP_GENERIC
376 << REGISTER_STAT_GROUP_RESPONSE_TIME
377 << REGISTER_STAT_GROUP_RSERPOOL
378 << REGISTER_TELEPHONY_GROUP_UNSORTED
379 << REGISTER_TELEPHONY_GROUP_ANSI
380 << REGISTER_TELEPHONY_GROUP_GSM
381 << REGISTER_TELEPHONY_GROUP_3GPP_UU
382 << REGISTER_TELEPHONY_GROUP_MTP3
383 << REGISTER_TELEPHONY_GROUP_SCTP
384 << REGISTER_TOOLS_GROUP_UNSORTED;
386 setWindowIcon(mainApp->normalIcon());
387 updateTitlebar();
388 setMenusForCaptureFile();
389 setForCapturedPackets(false);
390 setMenusForFileSet(false);
391 interfaceSelectionChanged();
392 loadWindowGeometry();
394 #ifndef HAVE_LUA
395 main_ui_->actionAnalyzeReloadLuaPlugins->setVisible(false);
396 #endif
398 qRegisterMetaType<FilterAction::Action>("FilterAction::Action");
399 qRegisterMetaType<FilterAction::ActionType>("FilterAction::ActionType");
400 connect(this, &WiresharkMainWindow::filterAction, this, &WiresharkMainWindow::queuedFilterAction, Qt::QueuedConnection);
402 //To prevent users use features before initialization complete
403 //Otherwise unexpected problems may occur
404 setFeaturesEnabled(false);
405 connect(mainApp, &MainApplication::appInitialized, this, [this]() { setFeaturesEnabled(); });
406 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::applyGlobalCommandLineOptions);
407 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::zoomText);
408 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::initViewColorizeMenu);
409 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::addStatsPluginsToMenu);
410 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::addDynamicMenus);
411 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::addPluginIFStructures);
412 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::initConversationMenus);
413 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::initExportObjectsMenus);
414 connect(mainApp, &MainApplication::appInitialized, this, &WiresharkMainWindow::initFollowStreamMenus);
415 connect(mainApp, &MainApplication::appInitialized, this,
416 [=]() { addDisplayFilterTranslationActions(main_ui_->menuEditCopy); });
418 connect(mainApp, &MainApplication::profileChanging, this, &WiresharkMainWindow::saveWindowGeometry);
419 connect(mainApp, &MainApplication::preferencesChanged, this, &WiresharkMainWindow::layoutPanes);
420 connect(mainApp, &MainApplication::preferencesChanged, this, &WiresharkMainWindow::layoutToolbars);
421 connect(mainApp, &MainApplication::preferencesChanged, this, &WiresharkMainWindow::updatePreferenceActions);
422 connect(mainApp, &MainApplication::preferencesChanged, this, &WiresharkMainWindow::zoomText);
423 connect(mainApp, &MainApplication::preferencesChanged, this, &WiresharkMainWindow::updateTitlebar);
425 connect(mainApp, &MainApplication::updateRecentCaptureStatus, this, &WiresharkMainWindow::updateRecentCaptures);
426 connect(mainApp, &MainApplication::preferencesChanged, this, &WiresharkMainWindow::updateRecentCaptures);
427 updateRecentCaptures();
429 #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN)
430 connect(mainApp, &MainApplication::softwareUpdateRequested, this, &WiresharkMainWindow::softwareUpdateRequested,
431 Qt::BlockingQueuedConnection);
432 #endif
434 df_combo_box_ = new DisplayFilterCombo(this);
436 funnel_statistics_ = new FunnelStatistics(this, capture_file_);
437 connect(df_combo_box_, &QComboBox::editTextChanged, funnel_statistics_, &FunnelStatistics::displayFilterTextChanged);
438 connect(funnel_statistics_, &FunnelStatistics::setDisplayFilter, this, &WiresharkMainWindow::setDisplayFilter);
439 connect(funnel_statistics_, &FunnelStatistics::openCaptureFile, this,
440 [=](QString cf_path, QString filter) { openCaptureFile(cf_path, filter); });
442 connect(df_combo_box_, &QComboBox::editTextChanged, this, &WiresharkMainWindow::updateDisplayFilterTranslationActions);
444 file_set_dialog_ = new FileSetDialog(this);
445 connect(file_set_dialog_, &FileSetDialog::fileSetOpenCaptureFile, this, [=](QString cf_path) { openCaptureFile(cf_path); });
447 initMainToolbarIcons();
449 main_ui_->displayFilterToolBar->insertWidget(main_ui_->actionNewDisplayFilterExpression, df_combo_box_);
451 // Make sure filter expressions overflow into a menu instead of a
452 // larger toolbar. We do this by adding them to a child toolbar.
453 // https://bugreports.qt.io/browse/QTBUG-2472
454 FilterExpressionToolBar *filter_expression_toolbar_ = new FilterExpressionToolBar(this);
455 connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterPreferences, this, &WiresharkMainWindow::onFilterPreferences);
456 connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterSelected, this, &WiresharkMainWindow::onFilterSelected);
457 connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterEdit, this, &WiresharkMainWindow::onFilterEdit);
459 main_ui_->displayFilterToolBar->addWidget(filter_expression_toolbar_);
461 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
462 connect(wireless_frame_, &WirelessFrame::showWirelessPreferences, this, &WiresharkMainWindow::showPreferencesDialog);
463 #endif
465 main_ui_->goToFrame->hide();
466 connect(main_ui_->goToFrame, &AccordionFrame::visibilityChanged, main_ui_->actionGoGoToPacket, &QAction::setChecked);
468 // XXX For some reason the cursor is drawn funny with an input mask set
469 // https://bugreports.qt-project.org/browse/QTBUG-7174
471 main_ui_->searchFrame->hide();
472 connect(main_ui_->searchFrame, &SearchFrame::visibilityChanged, main_ui_->actionEditFindPacket, &QAction::setChecked);
474 main_ui_->addressEditorFrame->hide();
475 main_ui_->columnEditorFrame->hide();
476 main_ui_->preferenceEditorFrame->hide();
477 main_ui_->filterExpressionFrame->hide();
479 #ifndef HAVE_LIBPCAP
480 main_ui_->menuCapture->setEnabled(false);
481 main_ui_->actionCaptureStart->setEnabled(false);
482 main_ui_->actionCaptureStop->setEnabled(false);
483 main_ui_->actionCaptureRestart->setEnabled(false);
484 main_ui_->actionCaptureOptions->setEnabled(false);
485 main_ui_->actionCaptureRefreshInterfaces->setEnabled(false);
486 #endif
488 // Set OS specific shortcuts for fullscreen mode
489 #if defined(Q_OS_MAC)
490 main_ui_->actionViewFullScreen->setShortcut(QKeySequence::FullScreen);
491 #else
492 main_ui_->actionViewFullScreen->setShortcut(QKeySequence(Qt::Key_F11));
493 #endif
495 #if defined(Q_OS_MAC)
497 main_ui_->goToPacketLabel->setAttribute(Qt::WA_MacSmallSize, true);
498 main_ui_->goToLineEdit->setAttribute(Qt::WA_MacSmallSize, true);
499 main_ui_->goToGo->setAttribute(Qt::WA_MacSmallSize, true);
500 main_ui_->goToCancel->setAttribute(Qt::WA_MacSmallSize, true);
502 main_ui_->actionEditPreferences->setMenuRole(QAction::PreferencesRole);
504 #endif // Q_OS_MAC
506 connect(main_ui_->goToGo, &QPushButton::pressed, this, &WiresharkMainWindow::goToGoClicked);
507 connect(main_ui_->goToCancel, &QPushButton::pressed, this, &WiresharkMainWindow::goToCancelClicked);
509 // A billion-1 is equivalent to the inputMask 900000000 previously used
510 // Avoid QValidator::Intermediate values by using a top value of all 9's
511 #define MAX_GOTO_LINE 999999999
513 QIntValidator *goToLineQiv = new QIntValidator(0,MAX_GOTO_LINE,this);
514 main_ui_->goToLineEdit->setValidator(goToLineQiv);
516 #ifdef HAVE_SOFTWARE_UPDATE
517 QAction *update_sep = main_ui_->menuHelp->insertSeparator(main_ui_->actionHelpAbout);
518 main_ui_->menuHelp->insertAction(update_sep, update_action_);
519 connect(update_action_, &QAction::triggered, this, &WiresharkMainWindow::checkForUpdates);
520 #endif
521 master_split_.setObjectName("splitterMaster");
522 extra_split_.setObjectName("splitterExtra");
523 master_split_.setChildrenCollapsible(false);
524 extra_split_.setChildrenCollapsible(false);
525 main_ui_->mainStack->addWidget(&master_split_);
527 empty_pane_.setObjectName("emptyPane");
528 empty_pane_.setVisible(false);
530 packet_list_ = new PacketList(&master_split_);
531 main_ui_->wirelessTimelineWidget->setPacketList(packet_list_);
532 connect(packet_list_, &PacketList::framesSelected, this, &WiresharkMainWindow::setMenusForSelectedPacket);
533 connect(packet_list_, &PacketList::framesSelected, this, &WiresharkMainWindow::framesSelected);
535 QAction *action = main_ui_->menuPacketComment->addAction(tr("Add New Comment…"));
536 connect(action, &QAction::triggered, this, &WiresharkMainWindow::addPacketComment);
537 action->setShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_C));
538 connect(main_ui_->menuPacketComment, &QMenu::aboutToShow, this, &WiresharkMainWindow::setEditCommentsMenu);
540 proto_tree_ = new ProtoTree(&master_split_);
541 proto_tree_->installEventFilter(this);
543 packet_list_->setProtoTree(proto_tree_);
544 packet_list_->setProfileSwitcher(profile_switcher_);
545 packet_list_->installEventFilter(this);
547 packet_diagram_ = new PacketDiagram(&master_split_);
549 main_stack_ = main_ui_->mainStack;
550 welcome_page_ = main_ui_->welcomePage;
551 main_status_bar_ = main_ui_->statusBar;
553 connect(proto_tree_, &ProtoTree::fieldSelected,
554 this, &WiresharkMainWindow::fieldSelected);
555 connect(packet_list_, &PacketList::fieldSelected,
556 this, &WiresharkMainWindow::fieldSelected);
557 connect(this, &WiresharkMainWindow::fieldSelected,
558 this, &WiresharkMainWindow::setMenusForSelectedTreeRow);
559 connect(this, &WiresharkMainWindow::fieldSelected,
560 main_ui_->statusBar, &MainStatusBar::selectedFieldChanged);
562 connect(this, &WiresharkMainWindow::fieldHighlight,
563 main_ui_->statusBar, &MainStatusBar::highlightedFieldChanged);
564 connect(mainApp, &WiresharkApplication::captureActive,
565 this, &WiresharkMainWindow::captureActive);
567 byte_view_tab_ = new ByteViewTab(&master_split_);
569 // Packet list and proto tree must exist before these are called.
570 setMenusForSelectedPacket();
571 setMenusForSelectedTreeRow();
573 initShowHideMainWidgets();
574 initTimeDisplayFormatMenu();
575 initTimePrecisionFormatMenu();
576 initFreezeActions();
577 updatePreferenceActions();
578 updateRecentActions();
579 setForCaptureInProgress(false);
581 setTabOrder(df_combo_box_->lineEdit(), packet_list_);
582 setTabOrder(packet_list_, proto_tree_);
584 connect(&capture_file_, &CaptureFile::captureEvent, this, &WiresharkMainWindow::captureEventHandler);
585 connect(&capture_file_, &CaptureFile::captureEvent, mainApp, &WiresharkApplication::captureEventHandler);
586 connect(&capture_file_, &CaptureFile::captureEvent, main_ui_->statusBar, &MainStatusBar::captureEventHandler);
587 connect(&capture_file_, &CaptureFile::captureEvent, profile_switcher_, &ProfileSwitcher::captureEventHandler);
589 connect(mainApp, &MainApplication::freezePacketList, packet_list_, &PacketList::freezePacketList);
590 connect(mainApp, &MainApplication::columnsChanged, packet_list_, &PacketList::columnsChanged);
591 connect(mainApp, &MainApplication::colorsChanged, packet_list_, &PacketList::colorsChanged);
592 connect(mainApp, &MainApplication::preferencesChanged, packet_list_, &PacketList::preferencesChanged);
593 connect(mainApp, &MainApplication::recentPreferencesRead, this, &WiresharkMainWindow::applyRecentPaneGeometry);
594 connect(mainApp, &MainApplication::recentPreferencesRead, this, &WiresharkMainWindow::updateRecentActions);
595 connect(mainApp, &MainApplication::packetDissectionChanged, this, &WiresharkMainWindow::redissectPackets, Qt::QueuedConnection);
597 connect(mainApp, &MainApplication::checkDisplayFilter, this, &WiresharkMainWindow::checkDisplayFilter);
598 connect(mainApp, &MainApplication::fieldsChanged, this, &WiresharkMainWindow::fieldsChanged);
599 connect(mainApp, &MainApplication::reloadLuaPlugins, this, &WiresharkMainWindow::reloadLuaPlugins);
601 connect(main_ui_->mainStack, &QStackedWidget::currentChanged, this, &WiresharkMainWindow::mainStackChanged);
603 connect(welcome_page_, &WelcomePage::startCapture, this, [this](QStringList interfaces) { startCapture(interfaces); });
604 connect(welcome_page_, &WelcomePage::recentFileActivated, this, [this](QString cfile) { openCaptureFile(cfile); });
606 connect(main_ui_->addressEditorFrame, &AddressEditorFrame::redissectPackets,
607 this, &WiresharkMainWindow::redissectPackets);
608 connect(main_ui_->addressEditorFrame, &AddressEditorFrame::showNameResolutionPreferences,
609 this, &WiresharkMainWindow::showPreferencesDialog);
610 connect(main_ui_->preferenceEditorFrame, &PreferenceEditorFrame::showProtocolPreferences,
611 this, &WiresharkMainWindow::showPreferencesDialog);
612 connect(main_ui_->filterExpressionFrame, &FilterExpressionFrame::showPreferencesDialog,
613 this, &WiresharkMainWindow::showPreferencesDialog);
614 connect(main_ui_->filterExpressionFrame, &FilterExpressionFrame::filterExpressionsChanged,
615 filter_expression_toolbar_, &FilterExpressionToolBar::filterExpressionsChanged);
617 /* Connect change of capture file */
618 connect(this, &WiresharkMainWindow::setCaptureFile,
619 main_ui_->searchFrame, &SearchFrame::setCaptureFile);
620 connect(this, &WiresharkMainWindow::setCaptureFile,
621 main_ui_->statusBar, &MainStatusBar::setCaptureFile);
622 connect(this, &WiresharkMainWindow::setCaptureFile,
623 packet_list_, &PacketList::setCaptureFile);
624 connect(this, &WiresharkMainWindow::setCaptureFile,
625 proto_tree_, &ProtoTree::setCaptureFile);
627 connect(mainApp, &MainApplication::zoomMonospaceFont, packet_list_, &PacketList::setMonospaceFont);
628 connect(mainApp, &MainApplication::zoomRegularFont, packet_list_, &PacketList::setRegularFont);
629 connect(mainApp, &MainApplication::zoomMonospaceFont, proto_tree_, &ProtoTree::setMonospaceFont);
631 connectFileMenuActions();
632 connectEditMenuActions();
633 connectViewMenuActions();
634 connectGoMenuActions();
635 connectCaptureMenuActions();
636 connectAnalyzeMenuActions();
637 connectStatisticsMenuActions();
638 connectTelephonyMenuActions();
639 connectWirelessMenuActions();
640 connectToolsMenuActions();
641 connectHelpMenuActions();
643 connect(packet_list_, &PacketList::packetDissectionChanged, this, &WiresharkMainWindow::redissectPackets);
644 connect(packet_list_, &PacketList::showColumnPreferences, this, &WiresharkMainWindow::showPreferencesDialog);
645 connect(packet_list_, &PacketList::showProtocolPreferences, this, &WiresharkMainWindow::showPreferencesDialog);
646 connect(packet_list_, SIGNAL(editProtocolPreference(pref_t*, module_t*)),
647 main_ui_->preferenceEditorFrame, SLOT(editPreference(pref_t*, module_t*)));
648 connect(packet_list_, &PacketList::editColumn, this, &WiresharkMainWindow::showColumnEditor);
649 connect(main_ui_->columnEditorFrame, &ColumnEditorFrame::columnEdited, packet_list_, &PacketList::columnsChanged);
650 connect(packet_list_, &QAbstractItemView::doubleClicked, this, [=](const QModelIndex &){ openPacketDialog(); });
651 connect(packet_list_, &PacketList::packetListScrolled, main_ui_->actionGoAutoScroll, &QAction::setChecked);
653 connect(proto_tree_, &ProtoTree::openPacketInNewWindow, this, &WiresharkMainWindow::openPacketDialog);
654 connect(proto_tree_, &ProtoTree::showProtocolPreferences, this, &WiresharkMainWindow::showPreferencesDialog);
655 connect(proto_tree_, SIGNAL(editProtocolPreference(pref_t*, module_t*)),
656 main_ui_->preferenceEditorFrame, SLOT(editPreference(pref_t*, module_t*)));
658 connect(main_ui_->statusBar, &MainStatusBar::showExpertInfo, this, [=]() {
659 statCommandExpertInfo(NULL, NULL);
662 connect(main_ui_->statusBar, &MainStatusBar::stopLoading,
663 &capture_file_, &CaptureFile::stopLoading);
665 connect(main_ui_->statusBar, &MainStatusBar::editCaptureComment,
666 main_ui_->actionStatisticsCaptureFileProperties, &QAction::trigger);
668 connect(main_ui_->menuApplyAsFilter, &QMenu::aboutToShow,
669 this, &WiresharkMainWindow::filterMenuAboutToShow);
670 connect(main_ui_->menuPrepareAFilter, &QMenu::aboutToShow,
671 this, &WiresharkMainWindow::filterMenuAboutToShow);
673 #ifdef HAVE_LIBPCAP
674 QTreeWidget *iface_tree = findChild<QTreeWidget *>("interfaceTree");
675 if (iface_tree) {
676 connect(iface_tree, &QTreeWidget::itemSelectionChanged, this, &WiresharkMainWindow::interfaceSelectionChanged);
678 connect(main_ui_->welcomePage, &WelcomePage::captureFilterSyntaxChanged,
679 this, &WiresharkMainWindow::captureFilterSyntaxChanged);
681 connect(this, &WiresharkMainWindow::showExtcapOptions, this, &WiresharkMainWindow::showExtcapOptionsDialog);
682 connect(this->welcome_page_, &WelcomePage::showExtcapOptions, this, &WiresharkMainWindow::showExtcapOptionsDialog);
684 #endif // HAVE_LIBPCAP
686 /* Create plugin_if hooks */
687 plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_APPLY, plugin_if_mainwindow_apply_filter);
688 plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_PREPARE, plugin_if_mainwindow_apply_filter);
689 plugin_if_register_gui_cb(PLUGIN_IF_PREFERENCE_SAVE, plugin_if_mainwindow_preference);
690 plugin_if_register_gui_cb(PLUGIN_IF_GOTO_FRAME, plugin_if_mainwindow_gotoframe);
691 #ifdef HAVE_LIBPCAP
692 plugin_if_register_gui_cb(PLUGIN_IF_GET_WS_INFO, plugin_if_mainwindow_get_ws_info);
693 #endif
694 plugin_if_register_gui_cb(PLUGIN_IF_GET_FRAME_DATA, plugin_if_mainwindow_get_frame_data);
695 plugin_if_register_gui_cb(PLUGIN_IF_GET_CAPTURE_FILE, plugin_if_mainwindow_get_capture_file);
696 plugin_if_register_gui_cb(PLUGIN_IF_REMOVE_TOOLBAR, plugin_if_mainwindow_update_toolbars);
698 /* Register Interface Toolbar callbacks */
699 iface_toolbar_register_cb(mainwindow_add_toolbar, mainwindow_remove_toolbar);
701 /* Show tooltips on menu items that go to websites */
702 main_ui_->actionHelpMPWireshark->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_WIRESHARK)));
703 main_ui_->actionHelpMPWireshark_Filter->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_WIRESHARK_FILTER)));
704 main_ui_->actionHelpMPWireshark_FilterReference->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_DFILTER_REF)));
705 main_ui_->actionHelpMPCapinfos->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_CAPINFOS)));
706 main_ui_->actionHelpMPDumpcap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_DUMPCAP)));
707 main_ui_->actionHelpMPEditcap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_EDITCAP)));
708 main_ui_->actionHelpMPMergecap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_MERGECAP)));
709 main_ui_->actionHelpMPRawshark->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_RAWSHARK)));
710 main_ui_->actionHelpMPReordercap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_REORDERCAP)));
711 main_ui_->actionHelpMPText2pcap->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_TEXT2PCAP)));
712 main_ui_->actionHelpMPTShark->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_TSHARK)));
714 main_ui_->actionHelpContents->setToolTip(gchar_free_to_qstring(topic_action_url(HELP_CONTENT)));
715 main_ui_->actionHelpWebsite->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_HOME)));
716 main_ui_->actionHelpFAQ->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_FAQ)));
717 main_ui_->actionHelpAsk->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_ASK)));
718 main_ui_->actionHelpDownloads->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_DOWNLOAD)));
719 main_ui_->actionHelpWiki->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_WIKI)));
720 main_ui_->actionHelpSampleCaptures->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_SAMPLE_CAPTURES)));
721 main_ui_->actionHelpReleaseNotes->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_RELEASE_NOTES)));
723 showWelcome();
726 WiresharkMainWindow::~WiresharkMainWindow()
728 disconnect(main_ui_->mainStack, 0, 0, 0);
729 if (previous_focus_ != nullptr) {
730 disconnect(previous_focus_, &QWidget::destroyed, this, &WiresharkMainWindow::resetPreviousFocus);
733 #ifndef Q_OS_MAC
734 // Below dialogs inherit GeometryStateDialog
735 // For reasons described in geometry_state_dialog.h no parent is set when
736 // instantiating the dialogs and as a result objects are not automatically
737 // freed by its parent. Free then here explicitly to avoid leak and numerous
738 // Valgrind complaints.
739 delete file_set_dialog_;
740 #ifdef HAVE_LIBPCAP
741 delete capture_options_dialog_;
742 #endif
744 #endif
745 delete main_ui_;
748 QMenu *WiresharkMainWindow::createPopupMenu()
750 QMenu *menu = new QMenu();
751 menu->addAction(main_ui_->actionViewMainToolbar);
752 menu->addAction(main_ui_->actionViewFilterToolbar);
753 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
754 menu->addAction(main_ui_->actionViewWirelessToolbar);
755 #endif
757 if (!main_ui_->menuInterfaceToolbars->actions().isEmpty()) {
758 QMenu *submenu = menu->addMenu(main_ui_->menuInterfaceToolbars->title());
759 foreach(QAction *action, main_ui_->menuInterfaceToolbars->actions()) {
760 submenu->addAction(action);
764 if (!main_ui_->menuAdditionalToolbars->actions().isEmpty()) {
765 QMenu *subMenu = menu->addMenu(main_ui_->menuAdditionalToolbars->title());
766 foreach(QAction *action, main_ui_->menuAdditionalToolbars->actions()) {
767 subMenu->addAction(action);
771 menu->addAction(main_ui_->actionViewStatusBar);
773 menu->addSeparator();
774 menu->addAction(main_ui_->actionViewPacketList);
775 menu->addAction(main_ui_->actionViewPacketDetails);
776 menu->addAction(main_ui_->actionViewPacketBytes);
777 menu->addAction(main_ui_->actionViewPacketDiagram);
778 return menu;
781 void WiresharkMainWindow::addInterfaceToolbar(const iface_toolbar *toolbar_entry)
783 QMenu *menu = main_ui_->menuInterfaceToolbars;
784 bool visible = g_list_find_custom(recent.interface_toolbars, toolbar_entry->menu_title, (GCompareFunc)strcmp) ? true : false;
786 QString title = QString().fromUtf8(toolbar_entry->menu_title);
787 QAction *action = new QAction(title, menu);
788 action->setEnabled(true);
789 action->setCheckable(true);
790 action->setChecked(visible);
791 action->setToolTip(tr("Show or hide the toolbar"));
793 QAction *before = NULL;
794 foreach(QAction *action, menu->actions()) {
795 // Ensure we add the menu entries in sorted order
796 if (action->text().compare(title, Qt::CaseInsensitive) > 0) {
797 before = action;
798 break;
801 menu->insertAction(before, action);
803 InterfaceToolbar *interface_toolbar = new InterfaceToolbar(this, toolbar_entry);
804 connect(mainApp, &MainApplication::appInitialized, interface_toolbar, &InterfaceToolbar::interfaceListChanged);
805 connect(mainApp, &MainApplication::localInterfaceListChanged, interface_toolbar, &InterfaceToolbar::interfaceListChanged);
807 QToolBar *toolbar = new QToolBar(this);
808 toolbar->addWidget(interface_toolbar);
809 toolbar->setMovable(false);
810 toolbar->setVisible(visible);
812 action->setData(QVariant::fromValue(toolbar));
814 addToolBar(Qt::TopToolBarArea, toolbar);
815 insertToolBarBreak(toolbar);
817 if (show_hide_actions_) {
818 show_hide_actions_->addAction(action);
821 menu->menuAction()->setVisible(true);
824 void WiresharkMainWindow::removeInterfaceToolbar(const char *menu_title)
826 QMenu *menu = main_ui_->menuInterfaceToolbars;
827 QAction *action = NULL;
828 QMap<QAction *, QWidget *>::iterator i;
830 QString title = QString().fromUtf8(menu_title);
831 foreach(action, menu->actions()) {
832 if (title.compare(action->text()) == 0) {
833 break;
837 if (action) {
838 if (show_hide_actions_) {
839 show_hide_actions_->removeAction(action);
841 menu->removeAction(action);
843 QToolBar *toolbar = action->data().value<QToolBar *>();
844 removeToolBar(toolbar);
846 delete action;
847 delete toolbar;
850 menu->menuAction()->setVisible(!menu->actions().isEmpty());
853 void WiresharkMainWindow::updateStyleSheet()
855 #ifdef Q_OS_MAC
856 // TODO: The event type QEvent::ApplicationPaletteChange is not sent to all child widgets.
857 // Workaround this by doing it manually for all AccordionFrame.
858 main_ui_->addressEditorFrame->updateStyleSheet();
859 main_ui_->columnEditorFrame->updateStyleSheet();
860 main_ui_->filterExpressionFrame->updateStyleSheet();
861 main_ui_->goToFrame->updateStyleSheet();
862 main_ui_->preferenceEditorFrame->updateStyleSheet();
863 main_ui_->searchFrame->updateStyleSheet();
865 df_combo_box_->updateStyleSheet();
866 welcome_page_->updateStyleSheets();
867 #endif
870 bool WiresharkMainWindow::eventFilter(QObject *obj, QEvent *event) {
872 // The user typed some text. Start filling in a filter.
873 // We may need to be more choosy here. We just need to catch events for the packet list,
874 // proto tree, and main welcome widgets.
875 if (event->type() == QEvent::KeyPress) {
876 QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
877 if (kevt->text().length() > 0 && kevt->text()[0].isPrint() &&
878 !(kevt->modifiers() & Qt::ControlModifier)) {
879 df_combo_box_->lineEdit()->insert(kevt->text());
880 df_combo_box_->lineEdit()->setFocus();
881 return true;
885 return QMainWindow::eventFilter(obj, event);
888 bool WiresharkMainWindow::event(QEvent *event)
890 switch (event->type()) {
891 case QEvent::ApplicationPaletteChange:
892 initMainToolbarIcons();
893 updateStyleSheet();
894 break;
895 default:
896 break;
899 return QMainWindow::event(event);
902 void WiresharkMainWindow::keyPressEvent(QKeyEvent *event) {
904 // Explicitly focus on the display filter combo.
905 if (event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_Slash) {
906 df_combo_box_->setFocus(Qt::ShortcutFocusReason);
907 return;
910 if (mainApp->focusWidget() == main_ui_->goToLineEdit) {
911 if (event->modifiers() == Qt::NoModifier || event->modifiers() == Qt::KeypadModifier) {
912 if (event->key() == Qt::Key_Escape) {
913 goToCancelClicked();
914 } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
915 goToGoClicked();
918 return; // goToLineEdit didn't want it and we don't either.
921 // Move up & down the packet list.
922 if (event->key() == Qt::Key_F7) {
923 packet_list_->goPreviousPacket();
924 } else if (event->key() == Qt::Key_F8) {
925 packet_list_->goNextPacket();
928 // Move along, citizen.
929 QMainWindow::keyPressEvent(event);
932 void WiresharkMainWindow::closeEvent(QCloseEvent *event) {
933 saveWindowGeometry();
935 /* If we're in the middle of stopping a capture, don't do anything;
936 the user can try deleting the window after the capture stops. */
937 /* Note the ordinary File->Quit menu item is disabled but it's always
938 * possible to close via other means (e.g. the window title bar.)
940 if (capture_stopping_) {
941 /* What if Wireshark is far behind the capture file and stopping is
942 taking a long time? (#19831) We could set the capture file state
943 to FILE_READ_ABORTED to make it stop quicker, but we might need to
944 warn about unsaved packets. We don't know if we're waiting to save
945 in testCaptureFileClose() having already warned about that, or if
946 the capture was stopped via other means. Call testCaptureFileClose
947 with special handling if capture_stopping_ is true? */
948 event->ignore();
949 return;
952 QString before_what(tr(" before quitting"));
953 if (!testCaptureFileClose(before_what, Quit)) {
954 event->ignore();
955 return;
958 #ifdef HAVE_LIBPCAP
959 if (capture_options_dialog_) capture_options_dialog_->close();
960 #endif
961 // Make sure we kill any open dumpcap processes.
962 delete welcome_page_;
964 // One of the many places we assume one main window.
965 if (!mainApp->isInitialized()) {
966 // If we're still initializing, QCoreApplication::quit() won't
967 // exit properly because we are not in the event loop. This
968 // means that the application won't clean up after itself. We
969 // might want to call mainApp->processEvents() during startup
970 // instead so that we can do a normal exit here.
971 exit(0);
973 mainApp->quit();
974 // When the main loop is not yet running (i.e. when openCaptureFile is
975 // executing in main.cpp), the above quit action has no effect.
976 // Schedule a quit action for the next execution of the main loop.
977 QMetaObject::invokeMethod(mainApp, "quit", Qt::QueuedConnection);
980 // XXX On windows the drag description is "Copy". It should be "Open" or
981 // "Merge" as appropriate. It looks like we need access to IDataObject in
982 // order to set DROPDESCRIPTION.
983 void WiresharkMainWindow::dragEnterEvent(QDragEnterEvent *event)
985 if (!event->mimeData()->hasUrls())
987 event->ignore();
988 return;
991 if (!main_ui_->actionFileOpen->isEnabled()) {
992 // We could alternatively call setAcceptDrops(!capture_in_progress)
993 // in setMenusForCaptureInProgress but that wouldn't provide feedback.
995 mainApp->pushStatus(WiresharkApplication::TemporaryStatus, tr("Unable to drop files during capture."));
996 event->setDropAction(Qt::IgnoreAction);
997 event->ignore();
998 return;
1001 bool have_files = false;
1002 foreach(QUrl drag_url, event->mimeData()->urls()) {
1003 if (!drag_url.toLocalFile().isEmpty()) {
1004 have_files = true;
1005 break;
1009 if (have_files) {
1010 event->acceptProposedAction();
1014 void WiresharkMainWindow::dropEvent(QDropEvent *event)
1016 if (!event->mimeData()->hasUrls())
1018 event->ignore();
1019 return;
1022 QList<QByteArray> local_files;
1023 int max_dropped_files = 100; // Arbitrary
1025 foreach(QUrl drop_url, event->mimeData()->urls()) {
1026 QString drop_file = drop_url.toLocalFile();
1027 if (!drop_file.isEmpty()) {
1028 local_files << drop_file.toUtf8();
1029 if (local_files.size() >= max_dropped_files) {
1030 break;
1035 event->acceptProposedAction();
1037 if (local_files.size() < 1) {
1038 event->ignore();
1039 return;
1042 event->accept();
1044 if (local_files.size() == 1) {
1045 openCaptureFile(local_files.at(0));
1046 return;
1049 const char **in_filenames = g_new(const char *, local_files.size());
1050 char *tmpname = NULL;
1052 for (int i = 0; i < local_files.size(); i++) {
1053 in_filenames[i] = local_files.at(i).constData();
1056 /* merge the files in chronological order */
1057 if (cf_merge_files_to_tempfile(this, global_capture_opts.temp_dir, &tmpname, static_cast<int>(local_files.size()),
1058 in_filenames,
1059 wtap_pcapng_file_type_subtype(),
1060 false) == CF_OK) {
1061 /* Merge succeeded; close the currently-open file and try
1062 to open the merged capture file. */
1063 openCaptureFile(tmpname, QString(), WTAP_TYPE_AUTO, true);
1066 g_free(tmpname);
1067 g_free(in_filenames);
1070 // Apply recent settings to the main window geometry.
1071 // We haven't loaded the preferences at this point so we assume that the
1072 // position and size preference are enabled.
1073 // Note we might end up with unexpected screen geometries if the user
1074 // unplugs or plugs in a monitor:
1075 // https://bugreports.qt.io/browse/QTBUG-44213
1076 void WiresharkMainWindow::loadWindowGeometry()
1078 int min_sensible_dimension = 200;
1080 #ifndef Q_OS_MAC
1081 if (recent.gui_geometry_main_maximized) {
1082 // [save|restore]Geometry does a better job (on Linux and Windows)
1083 // of restoring to the original monitor because it saves
1084 // QGuiApplication::screens().indexOf(screen())
1085 // (it also saves Qt::WindowFullScreen, restores the non-maximized
1086 // size even when starting out maximized, etc.)
1087 // Monitors of different DPI might still be tricky:
1088 // https://bugreports.qt.io/browse/QTBUG-70721
1089 // https://bugreports.qt.io/browse/QTBUG-77385
1091 // We might eventually want to always use restoreGeometry, but
1092 // for now at least use it just for maximized because it's better
1093 // then what we've been doing.
1094 if (recent.gui_geometry_main == nullptr ||
1095 !restoreGeometry(QByteArray::fromHex(recent.gui_geometry_main))) {
1097 setWindowState(Qt::WindowMaximized);
1099 } else
1100 #endif
1102 QRect recent_geom(recent.gui_geometry_main_x, recent.gui_geometry_main_y,
1103 recent.gui_geometry_main_width, recent.gui_geometry_main_height);
1104 if (!rect_on_screen(recent_geom)) {
1105 // We're not visible on any screens. See if we can move onscreen
1106 // without resizing.
1107 recent_geom.moveTo(50, 50); // recent.c defaults to 20.
1110 if (!rect_on_screen(recent_geom)) {
1111 // Give up and use the default geometry.
1112 return;
1115 // if (prefs.gui_geometry_save_position) {
1116 move(recent_geom.topLeft());
1117 // }
1119 if (// prefs.gui_geometry_save_size &&
1120 recent_geom.width() > min_sensible_dimension &&
1121 recent_geom.height() > min_sensible_dimension) {
1122 resize(recent_geom.size());
1127 void WiresharkMainWindow::saveWindowGeometry()
1129 if (prefs.gui_geometry_save_position ||
1130 prefs.gui_geometry_save_size ||
1131 prefs.gui_geometry_save_maximized) {
1132 g_free(recent.gui_geometry_main);
1133 recent.gui_geometry_main = g_strdup(saveGeometry().toHex().constData());
1136 if (prefs.gui_geometry_save_position) {
1137 recent.gui_geometry_main_x = pos().x();
1138 recent.gui_geometry_main_y = pos().y();
1141 if (prefs.gui_geometry_save_size) {
1142 recent.gui_geometry_main_width = size().width();
1143 recent.gui_geometry_main_height = size().height();
1146 if (prefs.gui_geometry_save_maximized) {
1147 // On macOS this is false when it shouldn't be
1148 // XXX: Does save/restoreGeometry work any better on macOS
1149 // for maximized windows? Apparently not:
1150 // https://bugreports.qt.io/browse/QTBUG-100272
1151 recent.gui_geometry_main_maximized = isMaximized();
1154 if (master_split_.sizes().length() > 0) {
1155 recent.gui_geometry_main_upper_pane = master_split_.sizes()[0];
1158 g_free(recent.gui_geometry_main_master_split);
1159 g_free(recent.gui_geometry_main_extra_split);
1160 recent.gui_geometry_main_master_split = g_strdup(master_split_.saveState().toHex().constData());
1161 recent.gui_geometry_main_extra_split = g_strdup(extra_split_.saveState().toHex().constData());
1163 // Saving the QSplitter state is more accurate (#19361), but save
1164 // the old GTK-style pane information for backwards compatibility
1165 // for switching back and forth with older versions.
1166 if (master_split_.sizes().length() > 2) {
1167 recent.gui_geometry_main_lower_pane = master_split_.sizes()[1];
1168 } else if (extra_split_.sizes().length() > 0) {
1169 recent.gui_geometry_main_lower_pane = extra_split_.sizes()[0];
1173 // Our event loop becomes nested whenever we call update_progress_dlg, which
1174 // includes several places in file.c. The GTK+ UI stays out of trouble by
1175 // showing a modal progress dialog. We attempt to do the equivalent below by
1176 // disabling parts of the main window. At a minimum the ProgressFrame in the
1177 // main status bar must remain accessible.
1179 // We might want to do this any time the main status bar progress frame is
1180 // shown and hidden.
1181 void WiresharkMainWindow::freeze()
1183 freeze_focus_ = mainApp->focusWidget();
1185 // XXX Alternatively we could just disable and enable the main menu.
1186 for (int i = 0; i < freeze_actions_.size(); i++) {
1187 QAction *action = freeze_actions_[i].first;
1188 freeze_actions_[i].second = action->isEnabled();
1189 action->setEnabled(false);
1191 main_ui_->centralWidget->setEnabled(false);
1194 void WiresharkMainWindow::thaw()
1196 main_ui_->centralWidget->setEnabled(true);
1197 for (int i = 0; i < freeze_actions_.size(); i++) {
1198 freeze_actions_[i].first->setEnabled(freeze_actions_[i].second);
1201 if (freeze_focus_) freeze_focus_->setFocus();
1202 freeze_focus_ = NULL;
1205 void WiresharkMainWindow::mergeCaptureFile()
1207 QString file_name = "";
1208 QString read_filter = "";
1209 dfilter_t *rfcode = NULL;
1210 int err;
1212 if (!capture_file_.capFile())
1213 return;
1215 if (prefs.gui_ask_unsaved) {
1216 if (cf_has_unsaved_data(capture_file_.capFile())) {
1217 QMessageBox msg_dialog;
1218 char *display_basename;
1219 int response;
1221 msg_dialog.setIcon(QMessageBox::Question);
1222 /* This file has unsaved data; ask the user whether to save
1223 the capture. */
1224 if (capture_file_.capFile()->is_tempfile) {
1225 msg_dialog.setText(tr("Save packets before merging?"));
1226 msg_dialog.setInformativeText(tr("A temporary capture file can't be merged."));
1227 } else {
1229 * Format the message.
1231 display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
1232 msg_dialog.setText(tr("Save changes in \"%1\" before merging?").arg(display_basename));
1233 g_free(display_basename);
1234 msg_dialog.setInformativeText(tr("Changes must be saved before the files can be merged."));
1237 msg_dialog.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
1238 msg_dialog.setDefaultButton(QMessageBox::Save);
1240 response = msg_dialog.exec();
1242 switch (response) {
1244 case QMessageBox::Save:
1245 /* Save the file but don't close it */
1246 saveCaptureFile(capture_file_.capFile(), false);
1247 break;
1249 case QMessageBox::Cancel:
1250 default:
1251 /* Don't do the merge. */
1252 return;
1257 for (;;) {
1258 CaptureFileDialog merge_dlg(this, capture_file_.capFile());
1259 int file_type;
1260 cf_status_t merge_status;
1261 char *in_filenames[2];
1262 char *tmpname;
1264 if (merge_dlg.merge(file_name, read_filter)) {
1265 df_error_t *df_err = NULL;
1267 if (!dfilter_compile(qUtf8Printable(read_filter), &rfcode, &df_err)) {
1268 /* Not valid. Tell the user, and go back and run the file
1269 selection box again once they dismiss the alert. */
1270 // Similar to commandline_info.jfilter section in main().
1271 QMessageBox::warning(this, tr("Invalid Read Filter"),
1272 tr("The filter expression \"%1\" isn't a valid read filter.\n(%2).").arg(read_filter, df_err->msg),
1273 QMessageBox::Ok);
1274 df_error_free(&df_err);
1275 continue;
1277 } else {
1278 return;
1281 file_type = capture_file_.capFile()->cd_t;
1283 /* Try to merge or append the two files */
1284 if (merge_dlg.mergeType() == 0) {
1285 /* chronological order */
1286 in_filenames[0] = g_strdup(capture_file_.capFile()->filename);
1287 in_filenames[1] = qstring_strdup(file_name);
1288 merge_status = cf_merge_files_to_tempfile(this, global_capture_opts.temp_dir, &tmpname, 2, in_filenames, file_type, false);
1289 } else if (merge_dlg.mergeType() <= 0) {
1290 /* prepend file */
1291 in_filenames[0] = qstring_strdup(file_name);
1292 in_filenames[1] = g_strdup(capture_file_.capFile()->filename);
1293 merge_status = cf_merge_files_to_tempfile(this, global_capture_opts.temp_dir, &tmpname, 2, in_filenames, file_type, true);
1294 } else {
1295 /* append file */
1296 in_filenames[0] = g_strdup(capture_file_.capFile()->filename);
1297 in_filenames[1] = qstring_strdup(file_name);
1298 merge_status = cf_merge_files_to_tempfile(this, global_capture_opts.temp_dir, &tmpname, 2, in_filenames, file_type, true);
1301 g_free(in_filenames[0]);
1302 g_free(in_filenames[1]);
1304 if (merge_status != CF_OK) {
1305 dfilter_free(rfcode);
1306 g_free(tmpname);
1307 continue;
1310 cf_close(capture_file_.capFile());
1312 /* Try to open the merged capture file. */
1313 // XXX - Just free rfcode and call
1314 // openCaptureFile(tmpname, read_filter, WTAP_TYPE_AUTO, true);
1315 CaptureFile::globalCapFile()->window = this;
1316 if (cf_open(CaptureFile::globalCapFile(), tmpname, WTAP_TYPE_AUTO, true /* temporary file */, &err) != CF_OK) {
1317 /* We couldn't open it; fail. */
1318 CaptureFile::globalCapFile()->window = NULL;
1319 dfilter_free(rfcode);
1320 g_free(tmpname);
1321 return;
1324 /* Attach the new read filter to "cf" ("cf_open()" succeeded, so
1325 it closed the previous capture file, and thus destroyed any
1326 previous read filter attached to "cf"). */
1327 cf_set_rfcode(CaptureFile::globalCapFile(), rfcode);
1329 switch (cf_read(CaptureFile::globalCapFile(), /*reloading=*/false)) {
1331 case CF_READ_OK:
1332 case CF_READ_ERROR:
1333 /* Just because we got an error, that doesn't mean we were unable
1334 to read any of the file; we handle what we could get from the
1335 file. */
1336 break;
1338 case CF_READ_ABORTED:
1339 /* The user bailed out of re-reading the capture file; the
1340 capture file has been closed - just free the capture file name
1341 string and return (without changing the last containing
1342 directory). */
1343 g_free(tmpname);
1344 return;
1347 /* This is a tempfile; don't change the last open directory. */
1348 g_free(tmpname);
1349 main_ui_->statusBar->showExpert();
1350 return;
1355 void WiresharkMainWindow::importCaptureFile() {
1356 ImportTextDialog import_dlg;
1358 QString before_what(tr(" before importing a capture"));
1359 if (!testCaptureFileClose(before_what))
1360 return;
1362 import_dlg.exec();
1364 if (import_dlg.result() != QDialog::Accepted) {
1365 showWelcome();
1366 return;
1369 openCaptureFile(import_dlg.capfileName(), QString(), WTAP_TYPE_AUTO, true);
1372 bool WiresharkMainWindow::saveCaptureFile(capture_file *cf, bool dont_reopen) {
1373 QString file_name;
1374 bool discard_comments;
1376 if (cf->is_tempfile) {
1377 /* This is a temporary capture file, so saving it means saving
1378 it to a permanent file. Prompt the user for a location
1379 to which to save it. Don't require that the file format
1380 support comments - if it's a temporary capture file, it's
1381 probably pcapng, which supports comments and, if it's
1382 not pcapng, let the user decide what they want to do
1383 if they've added comments. */
1384 return saveAsCaptureFile(cf, false, dont_reopen);
1385 } else {
1386 if (cf->unsaved_changes) {
1387 cf_write_status_t status;
1389 /* This is not a temporary capture file, but it has unsaved
1390 changes, so saving it means doing a "safe save" on top
1391 of the existing file, in the same format - no UI needed
1392 unless the file has comments and the file's format doesn't
1393 support them.
1395 If the file has comments, does the file's format support them?
1396 If not, ask the user whether they want to discard the comments
1397 or choose a different format. */
1398 switch (CaptureFileDialog::checkSaveAsWithComments(this, cf, cf->cd_t)) {
1400 case SAVE:
1401 /* The file can be saved in the specified format as is;
1402 just drive on and save in the format they selected. */
1403 discard_comments = false;
1404 break;
1406 case SAVE_WITHOUT_COMMENTS:
1407 /* The file can't be saved in the specified format as is,
1408 but it can be saved without the comments, and the user
1409 said "OK, discard the comments", so save it in the
1410 format they specified without the comments. */
1411 discard_comments = true;
1412 break;
1414 case SAVE_IN_ANOTHER_FORMAT:
1415 /* There are file formats in which we can save this that
1416 support comments, and the user said not to delete the
1417 comments. Do a "Save As" so the user can select
1418 one of those formats and choose a file name. */
1419 return saveAsCaptureFile(cf, true, dont_reopen);
1421 case CANCELLED:
1422 /* The user said "forget it". Just return. */
1423 return false;
1425 default:
1426 /* Squelch warnings that discard_comments is being used
1427 uninitialized. */
1428 ws_assert_not_reached();
1429 return false;
1432 /* XXX - cf->filename might get freed out from under us, because
1433 the code path through which cf_save_records() goes currently
1434 closes the current file and then opens and reloads the saved file,
1435 so make a copy and free it later. */
1436 file_name = cf->filename;
1437 status = cf_save_records(cf, qUtf8Printable(file_name), cf->cd_t, cf->compression_type,
1438 discard_comments, dont_reopen);
1439 switch (status) {
1441 case CF_WRITE_OK:
1442 /* The save succeeded; we're done.
1443 If we discarded comments, redraw the packet list to reflect
1444 any packets that no longer have comments. If we had unsaved
1445 changes, redraw the packet list, because saving a time
1446 shift zeroes out the frame.offset_shift field.
1447 If we had a color filter based on frame data, recolor. */
1448 /* XXX: If there is a filter based on those, we want to force
1449 a rescan with the current filter (we don't actually
1450 need to redissect.)
1452 if (discard_comments || cf->unsaved_changes) {
1453 if (color_filters_use_proto(proto_get_id_by_filter_name("frame"))) {
1454 packet_list_->recolorPackets();
1455 } else {
1456 packet_list_->redrawVisiblePackets();
1460 cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
1461 updateForUnsavedChanges(); // we update the title bar to remove the *
1462 break;
1464 case CF_WRITE_ERROR:
1465 /* The write failed.
1466 XXX - OK, what do we do now? Let them try a
1467 "Save As", in case they want to try to save to a
1468 different directory or file system? */
1469 break;
1471 case CF_WRITE_ABORTED:
1472 /* The write was aborted; just drive on. */
1473 return false;
1476 /* Otherwise just do nothing. */
1479 return true;
1482 bool WiresharkMainWindow::saveAsCaptureFile(capture_file *cf, bool must_support_comments, bool dont_reopen) {
1483 QString file_name = "";
1484 int file_type;
1485 wtap_compression_type compression_type;
1486 cf_write_status_t status;
1487 char *dirname;
1488 bool discard_comments = false;
1490 if (!cf) {
1491 return false;
1494 for (;;) {
1495 CaptureFileDialog save_as_dlg(this, cf);
1497 /* If the file has comments, does the format the user selected
1498 support them? If not, ask the user whether they want to
1499 discard the comments or choose a different format. */
1500 switch (save_as_dlg.saveAs(file_name, must_support_comments)) {
1502 case SAVE:
1503 /* The file can be saved in the specified format as is;
1504 just drive on and save in the format they selected. */
1505 discard_comments = false;
1506 break;
1508 case SAVE_WITHOUT_COMMENTS:
1509 /* The file can't be saved in the specified format as is,
1510 but it can be saved without the comments, and the user
1511 said "OK, discard the comments", so save it in the
1512 format they specified without the comments. */
1513 discard_comments = true;
1514 break;
1516 case SAVE_IN_ANOTHER_FORMAT:
1517 /* There are file formats in which we can save this that
1518 support comments, and the user said not to delete the
1519 comments. The combo box of file formats has had the
1520 formats that don't support comments trimmed from it,
1521 so run the dialog again, to let the user decide
1522 whether to save in one of those formats or give up. */
1523 must_support_comments = true;
1524 continue;
1526 case CANCELLED:
1527 /* The user said "forget it". Just get rid of the dialog box
1528 and return. */
1529 return false;
1531 file_type = save_as_dlg.selectedFileType();
1532 if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) {
1533 /* This "should not happen". */
1534 QMessageBox msg_dialog;
1536 msg_dialog.setIcon(QMessageBox::Critical);
1537 msg_dialog.setText(tr("Unknown file type returned by merge dialog."));
1538 msg_dialog.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
1539 msg_dialog.exec();
1540 return false;
1542 compression_type = save_as_dlg.compressionType();
1544 //#ifndef _WIN32
1545 // /* If the file exists and it's user-immutable or not writable,
1546 // ask the user whether they want to override that. */
1547 // if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) {
1548 // /* They don't. Let them try another file name or cancel. */
1549 // continue;
1550 // }
1551 //#endif
1553 /* Attempt to save the file */
1554 status = cf_save_records(cf, qUtf8Printable(file_name), file_type, compression_type,
1555 discard_comments, dont_reopen);
1556 switch (status) {
1558 case CF_WRITE_OK:
1559 /* The save succeeded; we're done. */
1560 /* Save the directory name for future file dialogs. */
1561 dirname = qstring_strdup(file_name); /* Overwrites cf_name */
1562 set_last_open_dir(get_dirname(dirname));
1563 g_free(dirname);
1564 /* The save succeeded; we're done.
1565 If we discarded comments, redraw the packet list to reflect
1566 any packets that no longer have comments. If we had unsaved
1567 changes, redraw the packet list, because saving a time
1568 shift zeroes out the frame.offset_shift field.
1569 If we had a color filter based on frame data, recolor. */
1570 /* XXX: If there is a filter based on those, we want to force
1571 a rescan with the current filter (we don't actually
1572 need to redissect.)
1574 if (discard_comments || cf->unsaved_changes) {
1575 if (color_filters_use_proto(proto_get_id_by_filter_name("frame"))) {
1576 packet_list_->recolorPackets();
1577 } else {
1578 packet_list_->redrawVisiblePackets();
1582 cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
1583 updateForUnsavedChanges(); // we update the title bar to remove the *
1584 /* Add this filename to the list of recent files in the "Recent Files" submenu */
1585 add_menu_recent_capture_file(qUtf8Printable(file_name), false);
1586 return true;
1588 case CF_WRITE_ERROR:
1589 /* The save failed; let the user try again. */
1590 continue;
1592 case CF_WRITE_ABORTED:
1593 /* The user aborted the save; just return. */
1594 return false;
1597 return true;
1600 void WiresharkMainWindow::exportSelectedPackets() {
1601 QString file_name = "";
1602 int file_type;
1603 wtap_compression_type compression_type;
1604 packet_range_t range;
1605 cf_write_status_t status;
1606 char *dirname;
1607 bool discard_comments = false;
1609 if (!capture_file_.capFile())
1610 return;
1612 /* Init the packet range */
1613 packet_range_init(&range, capture_file_.capFile());
1614 range.process_filtered = true;
1615 range.include_dependents = true;
1617 QList<int> rows = packet_list_->selectedRows(true);
1619 QStringList entries;
1620 foreach (int row, rows)
1621 entries << QString::number(row);
1622 QString selRange = entries.join(",");
1624 for (;;) {
1625 CaptureFileDialog esp_dlg(this, capture_file_.capFile());
1627 /* If the file has comments, does the format the user selected
1628 support them? If not, ask the user whether they want to
1629 discard the comments or choose a different format. */
1630 switch (esp_dlg.exportSelectedPackets(file_name, &range, selRange)) {
1632 case SAVE:
1633 /* The file can be saved in the specified format as is;
1634 just drive on and save in the format they selected. */
1635 discard_comments = false;
1636 break;
1638 case SAVE_WITHOUT_COMMENTS:
1639 /* The file can't be saved in the specified format as is,
1640 but it can be saved without the comments, and the user
1641 said "OK, discard the comments", so save it in the
1642 format they specified without the comments. */
1643 discard_comments = true;
1644 break;
1646 case SAVE_IN_ANOTHER_FORMAT:
1647 /* There are file formats in which we can save this that
1648 support comments, and the user said not to delete the
1649 comments. The combo box of file formats has had the
1650 formats that don't support comments trimmed from it,
1651 so run the dialog again, to let the user decide
1652 whether to save in one of those formats or give up. */
1653 continue;
1655 case CANCELLED:
1656 /* The user said "forget it". Just get rid of the dialog box
1657 and return. */
1658 goto cleanup;
1662 * Check that we're not going to save on top of the current
1663 * capture file.
1664 * We do it here so we catch all cases ...
1665 * Unfortunately, the file requester gives us an absolute file
1666 * name and the read file name may be relative (if supplied on
1667 * the command line). From Joerg Mayer.
1669 if (files_identical(capture_file_.capFile()->filename, qUtf8Printable(file_name))) {
1670 QMessageBox msg_box;
1671 char *display_basename = g_filename_display_basename(qUtf8Printable(file_name));
1673 msg_box.setIcon(QMessageBox::Critical);
1674 msg_box.setText(tr("Unable to export to \"%1\".").arg(display_basename));
1675 msg_box.setInformativeText(tr("You cannot export packets to the current capture file."));
1676 msg_box.setStandardButtons(QMessageBox::Ok);
1677 msg_box.setDefaultButton(QMessageBox::Ok);
1678 msg_box.exec();
1679 g_free(display_basename);
1680 continue;
1683 file_type = esp_dlg.selectedFileType();
1684 if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) {
1685 /* This "should not happen". */
1686 QMessageBox msg_box;
1688 msg_box.setIcon(QMessageBox::Critical);
1689 msg_box.setText(tr("Unknown file type returned by export dialog."));
1690 msg_box.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
1691 msg_box.exec();
1692 goto cleanup;
1694 compression_type = esp_dlg.compressionType();
1696 //#ifndef _WIN32
1697 // /* If the file exists and it's user-immutable or not writable,
1698 // ask the user whether they want to override that. */
1699 // if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) {
1700 // /* They don't. Let them try another file name or cancel. */
1701 // continue;
1702 // }
1703 //#endif
1705 /* Attempt to save the file */
1706 status = cf_export_specified_packets(capture_file_.capFile(), qUtf8Printable(file_name), &range, file_type, compression_type);
1707 switch (status) {
1709 case CF_WRITE_OK:
1710 /* The save succeeded; we're done. */
1711 /* Save the directory name for future file dialogs. */
1712 dirname = qstring_strdup(file_name); /* Overwrites cf_name */
1713 set_last_open_dir(get_dirname(dirname));
1714 g_free(dirname);
1715 /* If we discarded comments, redraw the packet list to reflect
1716 any packets that no longer have comments. */
1717 /* XXX: Why? We're exporting some packets to a new file but not
1718 changing our current capture file, that shouldn't change the
1719 current packet list. */
1720 if (discard_comments)
1721 packet_list_->redrawVisiblePackets();
1722 /* Add this filename to the list of recent files in the "Recent Files" submenu */
1723 add_menu_recent_capture_file(qUtf8Printable(file_name), false);
1724 goto cleanup;
1726 case CF_WRITE_ERROR:
1727 /* The save failed; let the user try again. */
1728 continue;
1730 case CF_WRITE_ABORTED:
1731 /* The user aborted the save; just return. */
1732 goto cleanup;
1736 cleanup:
1737 packet_range_cleanup(&range);
1740 void WiresharkMainWindow::exportDissections(export_type_e export_type) {
1741 capture_file *cf = capture_file_.capFile();
1742 g_return_if_fail(cf);
1744 QList<int> rows = packet_list_->selectedRows(true);
1746 QStringList entries;
1747 foreach (int row, rows)
1748 entries << QString::number(row);
1749 QString selRange = entries.join(",");
1751 ExportDissectionDialog *ed_dlg = new ExportDissectionDialog(this, cf, export_type, selRange);
1752 ed_dlg->setWindowModality(Qt::ApplicationModal);
1753 ed_dlg->setAttribute(Qt::WA_DeleteOnClose);
1754 ed_dlg->show();
1757 bool WiresharkMainWindow::testCaptureFileClose(QString before_what, FileCloseContext context) {
1758 bool capture_in_progress = false;
1759 bool do_close_file = false;
1761 if (!capture_file_.capFile() || capture_file_.capFile()->state == FILE_CLOSED)
1762 return true; /* Already closed, nothing to do */
1763 // XXX - capture_file_.capFile() returns NULL for a pending (not yet opened)
1764 // file in non real time mode. That's what we want for most of Wireshark
1765 // (because the capture_file* struct isn't fully set up crashes and other
1766 // oddities can occur, see #4035), but we need to examine it here.
1767 // We could use CaptureFile::globalCapFile(), or add a new member that
1768 // returns the capture_file* when it's pending but not when closed.
1770 if (capture_file_.capFile()->read_lock) {
1772 * If the file is being redissected, we cannot stop the capture since
1773 * that would crash and burn "cf_read", so stop early. Ideally all
1774 * callers should be modified to check this condition and act
1775 * accordingly (ignore action or queue it up), so print a warning.
1777 * setMenusForCaptureFile() disables most of the ways to reach
1778 * here (like Reload, Reload as File Format) when reading an file
1779 * (offline or live capture). Reload Lua Plugins is disabled and
1780 * enabled in a different place (because reloading Lua plugins
1781 * makes sense when there's no capture file, unlike those options),
1782 * although it's disabled while reading an offline file, not a live
1783 * capture (reloading Lua plugins only requires closing the file
1784 * if the current file is opened with a Lua FileHandler registered
1785 * to wiretap, which isn't the case with live captures as they only
1786 * use pcap and pcapng.)
1788 ws_warning("Refusing to close \"%s\" which is being read.", capture_file_.capFile()->filename);
1789 return false;
1792 #ifdef HAVE_LIBPCAP
1793 if (capture_file_.capFile()->state == FILE_READ_IN_PROGRESS ||
1794 capture_file_.capFile()->state == FILE_READ_PENDING) {
1796 * FILE_READ_IN_PROGRESS is true if we're reading a capture file
1797 * *or* if we're doing a live capture. From the capture file itself we
1798 * cannot differentiate the cases, so check the current capture session.
1799 * FILE_READ_PENDING is only used for a live capture, but it doesn't
1800 * hurt to check it here.
1802 capture_in_progress = captureSession()->state != CAPTURE_STOPPED;
1804 #endif
1806 if (prefs.gui_ask_unsaved) {
1807 if (cf_has_unsaved_data(capture_file_.capFile())) {
1808 // XXX - What if this a capture_in_progress in non real time mode?
1809 // Then the capture file isn't open and so it doesn't report
1810 // unsaved data. We need to check the captureSession count_pending.
1811 if (context == Update) {
1812 // We're being called from the software update window;
1813 // don't spawn yet another dialog. Just try again later.
1814 // XXX: The WinSparkle dialogs *aren't* modal, and a user
1815 // can bring Wireshark to the foreground, close/save the
1816 // file, and then click "Install Update" again, but it
1817 // seems like many users don't expect that (and also don't
1818 // know that Help->Check for Updates... exist, only knowing
1819 // about the automatic check.) See #17658 and duplicates.
1820 // Maybe we *should* spawn the dialog?
1821 return false;
1824 QMessageBox msg_dialog(this);
1825 QString question;
1826 QString infotext;
1827 QPushButton *save_button;
1828 QPushButton *discard_button;
1830 msg_dialog.setIcon(QMessageBox::Question);
1831 msg_dialog.setWindowTitle("Unsaved packets" UTF8_HORIZONTAL_ELLIPSIS);
1833 /* This file has unsaved data or there's a capture in
1834 progress; ask the user whether to save the data. */
1835 if (capture_in_progress && context != Restart) {
1836 question = tr("Do you want to stop the capture and save the captured packets%1?").arg(before_what);
1837 infotext = tr("Your captured packets will be lost if you don't save them.");
1838 } else if (capture_file_.capFile()->is_tempfile) {
1839 if (context == Reload) {
1840 // Reloading a tempfile will keep the packets, so this is not unsaved packets
1841 question = tr("Do you want to save the changes you've made%1?").arg(before_what);
1842 infotext = tr("Your changes will be lost if you don't save them.");
1843 } else {
1844 question = tr("Do you want to save the captured packets%1?").arg(before_what);
1845 infotext = tr("Your captured packets will be lost if you don't save them.");
1847 } else {
1848 // No capture in progress and not a tempfile, so this is not unsaved packets
1849 char *display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
1850 question = tr("Do you want to save the changes you've made to the capture file \"%1\"%2?").arg(display_basename, before_what);
1851 infotext = tr("Your changes will be lost if you don't save them.");
1852 g_free(display_basename);
1855 msg_dialog.setText(question);
1856 msg_dialog.setInformativeText(infotext);
1858 // XXX Text comes from ui/gtk/stock_icons.[ch]
1859 // Note that the button roles differ from the GTK+ version.
1860 // Cancel = RejectRole
1861 // Save = AcceptRole
1862 // Don't Save = DestructiveRole
1863 msg_dialog.addButton(QMessageBox::Cancel);
1865 if (capture_in_progress) {
1866 QString save_button_text;
1867 if (context == Restart) {
1868 save_button_text = tr("Save before Continue");
1869 } else {
1870 save_button_text = tr("Stop and Save");
1872 save_button = msg_dialog.addButton(save_button_text, QMessageBox::AcceptRole);
1873 } else {
1874 save_button = msg_dialog.addButton(QMessageBox::Save);
1876 msg_dialog.setDefaultButton(save_button);
1878 QString discard_button_text;
1879 if (capture_in_progress) {
1880 switch (context) {
1881 case Quit:
1882 discard_button_text = tr("Stop and Quit &without Saving");
1883 break;
1884 case Restart:
1885 discard_button_text = tr("Continue &without Saving");
1886 break;
1887 default:
1888 discard_button_text = tr("Stop and Continue &without Saving");
1889 break;
1891 } else {
1892 switch (context) {
1893 case Quit:
1894 discard_button_text = tr("Quit &without Saving");
1895 break;
1896 case Restart:
1897 default:
1898 discard_button_text = tr("Continue &without Saving");
1899 break;
1902 discard_button = msg_dialog.addButton(discard_button_text, QMessageBox::DestructiveRole);
1904 #if defined(Q_OS_MAC)
1906 * In macOS, the "default button" is not necessarily the
1907 * button that has the input focus; Enter/Return activates
1908 * the default button, and the spacebar activates the button
1909 * that has the input focus, and they might be different
1910 * buttons.
1912 * In a "do you want to save" dialog, for example, the
1913 * "save" button is the default button, and the "don't
1914 * save" button has the input focus, so you can press
1915 * Enter/Return to save or space not to save (or Escape
1916 * to dismiss the dialog).
1918 * In Qt terms, this means "no auto-default", as auto-default
1919 * makes the button with the input focus the default button,
1920 * so that Enter/Return will activate it.
1922 QList<QAbstractButton *> buttons = msg_dialog.buttons();
1923 for (int i = 0; i < buttons.size(); ++i) {
1924 QPushButton *button = static_cast<QPushButton *>(buttons.at(i));
1925 button->setAutoDefault(false);
1929 * It also means that the "don't save" button should be the one
1930 * initially given the focus.
1932 discard_button->setFocus();
1933 #endif
1935 * On Windows, if multiple Wireshark processes are open, another
1936 * application has focus, and "Close all [Wireshark] windows" is
1937 * chosen from the taskbar, we need to activate the window to
1938 * at least flash the taskbar (#16309).
1940 activateWindow();
1941 msg_dialog.exec();
1942 /* According to the Qt doc:
1943 * when using QMessageBox with custom buttons, exec() function returns an opaque value.
1945 * Therefore we should use clickedButton() to determine which button was clicked. */
1947 if (msg_dialog.clickedButton() == save_button) {
1948 #ifdef HAVE_LIBPCAP
1949 /* If there's a capture in progress, we have to stop the capture
1950 and then do the save. Wait until we dissect all the packets. */
1951 if (capture_in_progress)
1952 captureStop(false);
1953 #endif
1954 /* Save the file and close it */
1955 // XXX if no packets were captured, any unsaved comments set by
1956 // the user are silently discarded because capFile() is null.
1957 if (capture_file_.capFile() && saveCaptureFile(capture_file_.capFile(), true) == false)
1958 return false;
1959 do_close_file = true;
1960 } else if (msg_dialog.clickedButton() == discard_button) {
1961 /* Just close the file, discarding changes */
1962 do_close_file = true;
1963 } else {
1964 // cancelButton or some other unspecified button
1965 return false;
1967 } else {
1968 /* Unchanged file or capturing with no packets */
1969 do_close_file = true;
1971 } else {
1972 /* User asked not to be bothered by those prompts, just close it.
1973 XXX - should that apply only to saving temporary files? */
1974 do_close_file = true;
1978 * Are we done with this file and should we close the file?
1980 if (do_close_file) {
1981 #ifdef HAVE_LIBPCAP
1982 /* If there's a capture in progress, we have to stop the capture
1983 and then do the close. We don't care about unsaved packets,
1984 so we can abort the read. */
1985 if (capture_in_progress)
1986 captureStop(true);
1987 else if (capture_file_.capFile() && capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1989 * When an offline capture is being read, mark it as aborted.
1990 * cf_read will be responsible for actually closing the capture.
1992 * We cannot just invoke cf_close here since cf_read is up in the
1993 * call chain. (update_progress_dlg can end up processing the Quit
1994 * event from the user which then ends up here.)
1995 * See also the above "read_lock" check.
1997 * XXX - How do we get here with FILE_READ_IN_PROGRESS state but
1998 * read_lock false? Some time between cf_open and cf_read?
2000 capture_file_.capFile()->state = FILE_READ_ABORTED;
2001 return true;
2003 #endif
2004 /* Clear MainWindow file name details */
2005 gbl_cur_main_window_->setMwFileName("");
2007 /* captureStop() will close the file if not having any packets */
2008 if (capture_file_.capFile() && context != Restart && context != Reload)
2009 // Don't really close if Restart or Reload
2010 cf_close(capture_file_.capFile());
2013 return true; /* File closed */
2016 void WiresharkMainWindow::captureStop(bool discard) {
2017 if (discard && capture_file_.capFile()) {
2018 capture_file_.capFile()->state = FILE_READ_ABORTED;
2020 stopCapture();
2022 while (capture_file_.capFile() && (capture_file_.capFile()->state != FILE_READ_DONE &&
2023 capture_file_.capFile()->state != FILE_CLOSED)) {
2024 WiresharkApplication::processEvents();
2028 void WiresharkMainWindow::findTextCodecs() {
2029 const QList<int> mibs = QTextCodec::availableMibs();
2030 QRegularExpression ibmRegExp("^IBM([0-9]+).*$");
2031 QRegularExpression iso8859RegExp("^ISO-8859-([0-9]+).*$");
2032 QRegularExpression windowsRegExp("^WINDOWS-([0-9]+).*$");
2033 QRegularExpressionMatch match;
2034 for (int mib : mibs) {
2035 QTextCodec *codec = QTextCodec::codecForMib(mib);
2036 // QTextCodec::availableMibs() returns a list of hard-coded MIB
2037 // numbers, it doesn't check if they are really available. ICU data may
2038 // not have been compiled with support for all encodings.
2039 if (!codec) {
2040 continue;
2043 QString key = codec->name().toUpper();
2044 char rank;
2046 if (key.localeAwareCompare("IBM") < 0) {
2047 rank = 1;
2048 } else if ((match = ibmRegExp.match(key)).hasMatch()) {
2049 rank = match.captured(1).size(); // Up to 5
2050 } else if (key.localeAwareCompare("ISO-8859-") < 0) {
2051 rank = 6;
2052 } else if ((match = iso8859RegExp.match(key)).hasMatch()) {
2053 rank = 6 + match.captured(1).size(); // Up to 6 + 2
2054 } else if (key.localeAwareCompare("WINDOWS-") < 0) {
2055 rank = 9;
2056 } else if ((match = windowsRegExp.match(key)).hasMatch()) {
2057 rank = 9 + match.captured(1).size(); // Up to 9 + 4
2058 } else {
2059 rank = 14;
2061 // This doesn't perfectly well order the IBM codecs because it's
2062 // annoying to properly place IBM00858 and IBM00924 in the middle of
2063 // code page numbers not zero padded to 5 digits.
2064 // We could manipulate the key further to have more commonly used
2065 // charsets earlier. IANA MIB ordering would be unexpected:
2066 // https://www.iana.org/assignments/character-sets/character-sets.xml
2067 // For data about use in HTTP (other protocols can be quite different):
2068 // https://w3techs.com/technologies/overview/character_encoding
2070 key.prepend(char('0' + rank));
2071 // We use a map here because, due to backwards compatibility,
2072 // the same QTextCodec may be returned for multiple MIBs, which
2073 // happens for GBK/GB2312, EUC-KR/windows-949/UHC, and others.
2074 text_codec_map_.insert(key, codec);
2078 void WiresharkMainWindow::initMainToolbarIcons()
2080 // Normally 16 px. Reflects current GTK+ behavior and other Windows apps.
2081 int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize);
2082 #if !defined(Q_OS_WIN)
2083 // Force icons to 24x24 for now, otherwise actionFileOpen looks wonky.
2084 // The macOS HIG specifies 32-pixel icons but they're a little too
2085 // large IMHO.
2086 icon_size = icon_size * 3 / 2;
2087 #endif
2088 main_ui_->mainToolBar->setIconSize(QSize(icon_size, icon_size));
2090 // Toolbar actions. The GNOME HIG says that we should have a menu icon for each
2091 // toolbar item but that clutters up our menu. Set menu icons sparingly.
2093 main_ui_->actionCaptureStart->setIcon(StockIcon("x-capture-start"));
2094 main_ui_->actionCaptureStop->setIcon(StockIcon("x-capture-stop"));
2095 main_ui_->actionCaptureRestart->setIcon(StockIcon("x-capture-restart"));
2096 main_ui_->actionCaptureOptions->setIcon(StockIcon("x-capture-options"));
2098 // Menu icons are disabled in wireshark_main_window.ui for these File-> items.
2099 main_ui_->actionFileOpen->setIcon(StockIcon("document-open"));
2100 main_ui_->actionFileSave->setIcon(StockIcon("x-capture-file-save"));
2101 main_ui_->actionFileClose->setIcon(StockIcon("x-capture-file-close"));
2103 main_ui_->actionEditFindPacket->setIcon(StockIcon("edit-find"));
2104 main_ui_->actionGoPreviousPacket->setIcon(StockIcon("go-previous"));
2105 main_ui_->actionGoNextPacket->setIcon(StockIcon("go-next"));
2106 main_ui_->actionGoGoToPacket->setIcon(StockIcon("go-jump"));
2107 main_ui_->actionGoFirstPacket->setIcon(StockIcon("go-first"));
2108 main_ui_->actionGoLastPacket->setIcon(StockIcon("go-last"));
2109 main_ui_->actionGoPreviousConversationPacket->setIcon(StockIcon("go-previous"));
2110 main_ui_->actionGoNextConversationPacket->setIcon(StockIcon("go-next"));
2111 #if defined(Q_OS_MAC)
2112 main_ui_->actionGoPreviousConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Comma));
2113 main_ui_->actionGoNextConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Period));
2114 #endif
2115 main_ui_->actionGoFirstConversationPacket->setIcon(StockIcon("go-first"));
2116 main_ui_->actionGoLastConversationPacket->setIcon(StockIcon("go-last"));
2117 main_ui_->actionGoPreviousHistoryPacket->setIcon(StockIcon("go-previous"));
2118 main_ui_->actionGoNextHistoryPacket->setIcon(StockIcon("go-next"));
2119 main_ui_->actionGoAutoScroll->setIcon(StockIcon("x-stay-last"));
2121 main_ui_->actionViewColorizePacketList->setIcon(StockIcon("x-colorize-packets"));
2123 QList<QKeySequence> zi_seq = main_ui_->actionViewZoomIn->shortcuts();
2124 zi_seq << QKeySequence(Qt::CTRL | Qt::Key_Equal);
2125 main_ui_->actionViewZoomIn->setIcon(StockIcon("zoom-in"));
2126 main_ui_->actionViewZoomIn->setShortcuts(zi_seq);
2127 main_ui_->actionViewZoomOut->setIcon(StockIcon("zoom-out"));
2128 main_ui_->actionViewNormalSize->setIcon(StockIcon("zoom-original"));
2129 main_ui_->actionViewResizeColumns->setIcon(StockIcon("x-resize-columns"));
2130 main_ui_->actionViewResetLayout->setIcon(StockIcon("x-reset-layout_2"));
2131 main_ui_->actionViewReload->setIcon(StockIcon("x-capture-file-reload"));
2133 main_ui_->actionNewDisplayFilterExpression->setIcon(StockIcon("list-add"));
2136 void WiresharkMainWindow::initShowHideMainWidgets()
2138 if (show_hide_actions_) {
2139 return;
2142 show_hide_actions_ = new QActionGroup(this);
2143 QMap<QAction *, QWidget *> shmw_actions;
2145 show_hide_actions_->setExclusive(false);
2146 shmw_actions[main_ui_->actionViewMainToolbar] = main_ui_->mainToolBar;
2147 shmw_actions[main_ui_->actionViewFilterToolbar] = main_ui_->displayFilterToolBar;
2148 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
2149 shmw_actions[main_ui_->actionViewWirelessToolbar] = main_ui_->wirelessToolBar;
2150 #endif
2151 shmw_actions[main_ui_->actionViewStatusBar] = main_ui_->statusBar;
2152 shmw_actions[main_ui_->actionViewPacketList] = packet_list_;
2153 shmw_actions[main_ui_->actionViewPacketDetails] = proto_tree_;
2154 shmw_actions[main_ui_->actionViewPacketBytes] = byte_view_tab_;
2155 shmw_actions[main_ui_->actionViewPacketDiagram] = packet_diagram_;
2157 foreach(QAction *shmwa, shmw_actions.keys()) {
2158 shmwa->setData(QVariant::fromValue(shmw_actions[shmwa]));
2159 show_hide_actions_->addAction(shmwa);
2162 // Initial hide the Interface Toolbar submenu
2163 main_ui_->menuInterfaceToolbars->menuAction()->setVisible(false);
2165 /* Initially hide the additional toolbars menus */
2166 main_ui_->menuAdditionalToolbars->menuAction()->setVisible(false);
2168 connect(show_hide_actions_, &QActionGroup::triggered, this, &WiresharkMainWindow::showHideMainWidgets);
2171 void WiresharkMainWindow::initTimeDisplayFormatMenu()
2173 if (time_display_actions_) {
2174 return;
2177 time_display_actions_ = new QActionGroup(this);
2179 td_actions[main_ui_->actionViewTimeDisplayFormatDateYMDandTimeOfDay] = TS_ABSOLUTE_WITH_YMD;
2180 td_actions[main_ui_->actionViewTimeDisplayFormatDateYDOYandTimeOfDay] = TS_ABSOLUTE_WITH_YDOY;
2181 td_actions[main_ui_->actionViewTimeDisplayFormatTimeOfDay] = TS_ABSOLUTE;
2182 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceEpoch] = TS_EPOCH;
2183 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceFirstCapturedPacket] = TS_RELATIVE;
2184 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousCapturedPacket] = TS_DELTA;
2185 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousDisplayedPacket] = TS_DELTA_DIS;
2186 td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYMDandTimeOfDay] = TS_UTC_WITH_YMD;
2187 td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYDOYandTimeOfDay] = TS_UTC_WITH_YDOY;
2188 td_actions[main_ui_->actionViewTimeDisplayFormatUTCTimeOfDay] = TS_UTC;
2190 foreach(QAction* tda, td_actions.keys()) {
2191 tda->setData(QVariant::fromValue(td_actions[tda]));
2192 time_display_actions_->addAction(tda);
2195 connect(time_display_actions_, &QActionGroup::triggered, this, &WiresharkMainWindow::setTimestampFormat);
2198 void WiresharkMainWindow::initTimePrecisionFormatMenu()
2200 if (time_precision_actions_) {
2201 return;
2204 time_precision_actions_ = new QActionGroup(this);
2206 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionAutomatic] = TS_PREC_AUTO;
2207 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionSeconds] = TS_PREC_FIXED_SEC;
2208 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision100Milliseconds] = TS_PREC_FIXED_100_MSEC;
2209 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision10Milliseconds] = TS_PREC_FIXED_10_MSEC;
2210 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMilliseconds] = TS_PREC_FIXED_MSEC;
2211 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision100Microseconds] = TS_PREC_FIXED_100_USEC;
2212 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision10Microseconds] = TS_PREC_FIXED_10_USEC;
2213 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMicroseconds] = TS_PREC_FIXED_USEC;
2214 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision100Nanoseconds] = TS_PREC_FIXED_100_NSEC;
2215 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecision10Nanoseconds] = TS_PREC_FIXED_10_NSEC;
2216 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionNanoseconds] = TS_PREC_FIXED_NSEC;
2218 foreach(QAction* tpa, tp_actions.keys()) {
2219 tpa->setData(QVariant::fromValue(tp_actions[tpa]));
2220 time_precision_actions_->addAction(tpa);
2223 connect(time_precision_actions_, &QActionGroup::triggered, this, &WiresharkMainWindow::setTimestampPrecision);
2226 // Menu items which will be disabled when we freeze() and whose state will
2227 // be restored when we thaw(). Add to the list as needed.
2228 void WiresharkMainWindow::initFreezeActions()
2230 QList<QAction *> freeze_actions = QList<QAction *>()
2231 << main_ui_->actionFileClose
2232 << main_ui_->actionViewReload
2233 << main_ui_->actionEditMarkSelected
2234 << main_ui_->actionEditMarkAllDisplayed
2235 << main_ui_->actionEditUnmarkAllDisplayed
2236 << main_ui_->actionEditIgnoreSelected
2237 << main_ui_->actionEditIgnoreAllDisplayed
2238 << main_ui_->actionEditUnignoreAllDisplayed
2239 << main_ui_->actionEditSetTimeReference
2240 << main_ui_->actionEditUnsetAllTimeReferences;
2242 foreach(QAction *action, freeze_actions) {
2243 freeze_actions_ << QPair<QAction *, bool>(action, false);
2247 void WiresharkMainWindow::initConversationMenus()
2249 int i;
2251 QList<QAction *> cc_actions = QList<QAction *>()
2252 << main_ui_->actionViewColorizeConversation1 << main_ui_->actionViewColorizeConversation2
2253 << main_ui_->actionViewColorizeConversation3 << main_ui_->actionViewColorizeConversation4
2254 << main_ui_->actionViewColorizeConversation5 << main_ui_->actionViewColorizeConversation6
2255 << main_ui_->actionViewColorizeConversation7 << main_ui_->actionViewColorizeConversation8
2256 << main_ui_->actionViewColorizeConversation9 << main_ui_->actionViewColorizeConversation10;
2258 packet_list_->conversationMenu()->clear();
2259 packet_list_->colorizeMenu()->clear();
2260 proto_tree_->colorizeMenu()->clear();
2261 main_ui_->menuConversationFilter->clear();
2263 for (GList *conv_filter_list_entry = packet_conv_filter_list; conv_filter_list_entry; conv_filter_list_entry = gxx_list_next(conv_filter_list_entry)) {
2264 // Main menu items
2265 conversation_filter_t* conv_filter = gxx_list_data(conversation_filter_t *, conv_filter_list_entry);
2266 ConversationAction *conv_action = new ConversationAction(main_ui_->menuConversationFilter, conv_filter);
2267 main_ui_->menuConversationFilter->addAction(conv_action);
2269 connect(this, &WiresharkMainWindow::packetInfoChanged, conv_action, &ConversationAction::setPacketInfo);
2270 connect(conv_action, &ConversationAction::triggered, this, &WiresharkMainWindow::applyConversationFilter, Qt::QueuedConnection);
2272 // Packet list context menu items
2273 packet_list_->conversationMenu()->addAction(conv_action);
2275 QMenu *submenu = packet_list_->colorizeMenu()->addMenu(conv_action->text());
2276 i = 1;
2278 foreach(QAction *cc_action, cc_actions) {
2279 conv_action = new ConversationAction(submenu, conv_filter);
2280 conv_action->setText(cc_action->text());
2281 conv_action->setIcon(cc_action->icon());
2282 conv_action->setColorNumber(i++);
2283 submenu->addAction(conv_action);
2284 connect(this, &WiresharkMainWindow::packetInfoChanged, conv_action, &ConversationAction::setPacketInfo);
2285 connect(conv_action, &ConversationAction::triggered, this, &WiresharkMainWindow::colorizeActionTriggered);
2288 conv_action = new ConversationAction(submenu, conv_filter);
2289 conv_action->setText(main_ui_->actionViewColorizeNewColoringRule->text());
2290 submenu->addAction(conv_action);
2291 connect(this, &WiresharkMainWindow::packetInfoChanged, conv_action, &ConversationAction::setPacketInfo);
2292 connect(conv_action, &ConversationAction::triggered, this, &WiresharkMainWindow::colorizeActionTriggered);
2294 // Proto tree conversation menu is filled in in ProtoTree::contextMenuEvent.
2295 // We should probably do that here.
2296 // XXX - Or we should create all the menus in the contextMenuEvents.
2297 // Note that the packet list and proto tree menu items created here are
2298 // not updated automatically on language change. (The main menu items,
2299 // as members of main_ui_ are, in WiresharkMainWindow::changeEvent,)
2300 // #19997
2303 // Proto tree colorization items
2304 i = 1;
2305 ColorizeAction *colorize_action;
2306 foreach(QAction *cc_action, cc_actions) {
2307 colorize_action = new ColorizeAction(proto_tree_->colorizeMenu());
2308 colorize_action->setText(cc_action->text());
2309 colorize_action->setIcon(cc_action->icon());
2310 colorize_action->setColorNumber(i++);
2311 proto_tree_->colorizeMenu()->addAction(colorize_action);
2312 connect(this, &WiresharkMainWindow::fieldFilterChanged, colorize_action, &ColorizeAction::setFieldFilter);
2313 connect(colorize_action, &ColorizeAction::triggered, this, &WiresharkMainWindow::colorizeActionTriggered);
2316 colorize_action = new ColorizeAction(proto_tree_->colorizeMenu());
2317 colorize_action->setText(main_ui_->actionViewColorizeNewColoringRule->text());
2318 proto_tree_->colorizeMenu()->addAction(colorize_action);
2319 connect(this, &WiresharkMainWindow::fieldFilterChanged, colorize_action, &ColorizeAction::setFieldFilter);
2320 connect(colorize_action, &ColorizeAction::triggered, this, &WiresharkMainWindow::colorizeActionTriggered);
2323 bool WiresharkMainWindow::addExportObjectsMenuItem(const void *, void *value, void *userdata)
2325 register_eo_t *eo = (register_eo_t*)value;
2326 WiresharkMainWindow *window = (WiresharkMainWindow*)userdata;
2328 ExportObjectAction *export_action = new ExportObjectAction(window->main_ui_->menuFileExportObjects, eo);
2329 window->main_ui_->menuFileExportObjects->addAction(export_action);
2331 //initially disable until a file is loaded (then file signals will take over)
2332 export_action->setEnabled(false);
2334 connect(&window->capture_file_, &CaptureFile::captureEvent, export_action, &ExportObjectAction::captureFileEvent);
2335 connect(export_action, &ExportObjectAction::triggered, window, &WiresharkMainWindow::applyExportObject);
2336 return false;
2339 void WiresharkMainWindow::initExportObjectsMenus()
2341 eo_iterate_tables(addExportObjectsMenuItem, this);
2344 bool WiresharkMainWindow::addFollowStreamMenuItem(const void *key, void *value, void *userdata)
2346 const char *short_name = (const char*)key;
2347 register_follow_t *follow = (register_follow_t*)value;
2348 WiresharkMainWindow *window = (WiresharkMainWindow*)userdata;
2350 FollowStreamAction *follow_action = new FollowStreamAction(window->main_ui_->menuFollow, follow);
2351 window->main_ui_->menuFollow->addAction(follow_action);
2353 follow_action->setEnabled(false);
2355 /* Special features for some of the built in follow types, like
2356 * shortcuts and overriding the name. XXX: Should these go in
2357 * FollowStreamAction, or should some of these (e.g. TCP and UDP)
2358 * be registered in initFollowStreamMenus so that they can be
2359 * on the top of the menu list too?
2361 if (g_strcmp0(short_name, "TCP") == 0) {
2362 follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_T);
2363 } else if (g_strcmp0(short_name, "UDP") == 0) {
2364 follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_U);
2365 } else if (g_strcmp0(short_name, "DCCP") == 0) {
2366 /* XXX: Not sure this one is widely enough used to need a shortcut. */
2367 follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_E);
2368 } else if (g_strcmp0(short_name, "TLS") == 0) {
2369 follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_S);
2370 } else if (g_strcmp0(short_name, "HTTP") == 0) {
2371 follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_H);
2372 } else if (g_strcmp0(short_name, "HTTP2") == 0) {
2373 follow_action->setText(tr("HTTP/2 Stream"));
2374 } else if (g_strcmp0(short_name, "SIP") == 0) {
2375 follow_action->setText(tr("SIP Call"));
2376 } else if (g_strcmp0(short_name, "USBCOM") == 0) {
2377 follow_action->setText(tr("USB CDC Data"));
2380 connect(follow_action, &QAction::triggered, window,
2381 [window, follow]() { window->openFollowStreamDialog(get_follow_proto_id(follow)); },
2382 Qt::QueuedConnection);
2383 return false;
2386 void WiresharkMainWindow::initFollowStreamMenus()
2388 /* This puts them all in the menus in alphabetical order. */
2389 follow_iterate_followers(addFollowStreamMenuItem, this);
2392 // Titlebar
2393 void WiresharkMainWindow::setTitlebarForCaptureFile()
2395 use_capturing_title_ = false;
2396 updateTitlebar();
2399 QString WiresharkMainWindow::replaceWindowTitleVariables(QString title)
2401 title.replace("%P", get_profile_name());
2402 title.replace("%V", get_ws_vcs_version_info());
2404 #ifdef HAVE_LIBPCAP
2405 if (global_commandline_info.capture_comments) {
2406 // Use the first capture comment from command line.
2407 title.replace("%C", (char *)g_ptr_array_index(global_commandline_info.capture_comments, 0));
2408 } else {
2409 // No capture comment.
2410 title.remove("%C");
2412 #else
2413 title.remove("%C");
2414 #endif
2416 if (title.contains("%F")) {
2417 // %F is file path of the capture file.
2418 if (capture_file_.capFile()) {
2419 // get_dirname() will overwrite the argument so make a copy first
2420 char *filename = g_strdup(capture_file_.capFile()->filename);
2421 QString file(get_dirname(filename));
2422 g_free(filename);
2423 #ifndef _WIN32
2424 // Substitute HOME with ~
2425 QString homedir(g_getenv("HOME"));
2426 if (!homedir.isEmpty()) {
2427 homedir.remove(QRegularExpression("[/]+$"));
2428 file.replace(homedir, "~");
2430 #endif
2431 title.replace("%F", file);
2432 } else {
2433 // No file loaded, no folder name
2434 title.remove("%F");
2438 if (title.contains("%S")) {
2439 // %S is a conditional separator (" - ") that only shows when surrounded by variables
2440 // with values or static text. Remove repeating, leading and trailing separators.
2441 title.replace(QRegularExpression("(%S)+"), "%S");
2442 title.remove(QRegularExpression("^%S|%S$"));
2443 #ifdef __APPLE__
2444 // On macOS we separate with a unicode em dash
2445 title.replace("%S", " " UTF8_EM_DASH " ");
2446 #else
2447 title.replace("%S", " - ");
2448 #endif
2451 return title;
2454 void WiresharkMainWindow::setWSWindowTitle(QString title)
2456 if (title.isEmpty()) {
2457 title = tr("The Wireshark Network Analyzer");
2460 if (prefs.gui_prepend_window_title && prefs.gui_prepend_window_title[0]) {
2461 QString custom_title = replaceWindowTitleVariables(prefs.gui_prepend_window_title);
2462 if (custom_title.length() > 0) {
2463 title.prepend(QStringLiteral("[%1] ").arg(custom_title));
2467 if (prefs.gui_window_title && prefs.gui_window_title[0]) {
2468 QString custom_title = replaceWindowTitleVariables(prefs.gui_window_title);
2469 if (custom_title.length() > 0) {
2470 #ifdef __APPLE__
2471 // On macOS we separate the titles with a unicode em dash
2472 title.append(QStringLiteral(" %1 %2").arg(UTF8_EM_DASH).arg(custom_title));
2473 #else
2474 title.append(QStringLiteral(" [%1]").arg(custom_title));
2475 #endif
2479 setWindowTitle(title);
2480 setWindowFilePath(NULL);
2483 void WiresharkMainWindow::setTitlebarForCaptureInProgress()
2485 use_capturing_title_ = true;
2486 updateTitlebar();
2489 void WiresharkMainWindow::updateTitlebar()
2491 if (use_capturing_title_ && capture_file_.capFile()) {
2492 setWSWindowTitle(tr("Capturing from %1").arg(cf_get_tempfile_source(capture_file_.capFile())));
2493 } else if (capture_file_.capFile() && capture_file_.capFile()->filename) {
2494 setWSWindowTitle(QStringLiteral("[*]%1").arg(capture_file_.fileDisplayName()));
2496 // XXX - on non-Mac platforms, put in the application
2497 // name? Or do so only for temporary files?
2499 if (!capture_file_.capFile()->is_tempfile) {
2501 // Set the file path; that way, for macOS, it'll set the
2502 // "proxy icon".
2504 setWindowFilePath(capture_file_.filePath());
2506 setWindowModified(cf_has_unsaved_data(capture_file_.capFile()));
2507 } else {
2508 /* We have no capture file. */
2509 setWSWindowTitle();
2513 // Menu state
2515 /* Enable or disable menu items based on whether you have a capture file
2516 you've finished reading and, if you have one, whether it's been saved
2517 and whether it could be saved except by copying the raw packet data. */
2518 void WiresharkMainWindow::setMenusForCaptureFile(bool force_disable)
2520 bool enable = true;
2521 bool can_write = false;
2522 bool can_save = false;
2523 bool can_save_as = false;
2525 if (force_disable || capture_file_.capFile() == NULL || capture_file_.capFile()->state == FILE_READ_IN_PROGRESS || capture_file_.capFile()->state == FILE_READ_PENDING) {
2526 /* We have no capture file or we're currently reading a file */
2527 enable = false;
2528 } else {
2529 /* We have a capture file. Can we write or save? */
2530 can_write = cf_can_write_with_wiretap(capture_file_.capFile());
2531 can_save = cf_can_save(capture_file_.capFile());
2532 can_save_as = cf_can_save_as(capture_file_.capFile());
2535 main_ui_->actionViewReload_as_File_Format_or_Capture->setEnabled(enable);
2536 main_ui_->actionFileMerge->setEnabled(can_write);
2537 main_ui_->actionFileClose->setEnabled(enable);
2538 main_ui_->actionFileSave->setEnabled(can_save);
2539 main_ui_->actionFileSaveAs->setEnabled(can_save_as);
2540 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(enable);
2541 /* The Protocol Hierarchy statistics run on all the packets that
2542 * pass the current filter, don't enable if a read or rescan is
2543 * still in progress.
2545 main_ui_->actionStatisticsProtocolHierarchy->setEnabled(enable);
2547 * "Export Specified Packets..." should be available only if
2548 * we can write the file out in at least one format.
2550 main_ui_->actionFileExportPackets->setEnabled(can_write);
2552 main_ui_->actionFileExportAsCArrays->setEnabled(enable);
2553 main_ui_->actionFileExportAsCSV->setEnabled(enable);
2554 main_ui_->actionFileExportAsPDML->setEnabled(enable);
2555 main_ui_->actionFileExportAsPlainText->setEnabled(enable);
2556 main_ui_->actionFileExportAsPSML->setEnabled(enable);
2557 main_ui_->actionFileExportAsJSON->setEnabled(enable);
2559 main_ui_->actionFileExportPDU->setEnabled(enable);
2560 main_ui_->actionFileStripHeaders->setEnabled(enable);
2561 /* XXX: "Export TLS Session Keys..." should be enabled only if
2562 * ssl_session_key_count() > 0.
2564 main_ui_->actionFileExportTLSSessionKeys->setEnabled(enable);
2566 foreach(QAction *eo_action, main_ui_->menuFileExportObjects->actions()) {
2567 eo_action->setEnabled(enable);
2570 main_ui_->actionViewReload->setEnabled(enable);
2572 #ifdef HAVE_SOFTWARE_UPDATE
2573 // We might want to enable or disable automatic checks here as well.
2574 update_action_->setEnabled(!can_save);
2575 #endif
2578 void WiresharkMainWindow::setMenusForCaptureInProgress(bool capture_in_progress) {
2579 /* Either a capture was started or stopped; in either case, it's not
2580 in the process of stopping, so allow quitting. */
2582 main_ui_->actionFileOpen->setEnabled(!capture_in_progress);
2583 main_ui_->menuOpenRecentCaptureFile->setEnabled(!capture_in_progress);
2585 main_ui_->actionFileExportAsCArrays->setEnabled(capture_in_progress);
2586 main_ui_->actionFileExportAsCSV->setEnabled(capture_in_progress);
2587 main_ui_->actionFileExportAsPDML->setEnabled(capture_in_progress);
2588 main_ui_->actionFileExportAsPlainText->setEnabled(capture_in_progress);
2589 main_ui_->actionFileExportAsPSML->setEnabled(capture_in_progress);
2590 main_ui_->actionFileExportAsJSON->setEnabled(capture_in_progress);
2592 main_ui_->actionFileExportPDU->setEnabled(!capture_in_progress);
2593 main_ui_->actionFileStripHeaders->setEnabled(!capture_in_progress);
2594 main_ui_->actionFileExportTLSSessionKeys->setEnabled(capture_in_progress);
2596 foreach(QAction *eo_action, main_ui_->menuFileExportObjects->actions()) {
2597 eo_action->setEnabled(capture_in_progress);
2600 main_ui_->menuFileSet->setEnabled(!capture_in_progress);
2601 main_ui_->actionFileQuit->setEnabled(true);
2602 #ifdef HAVE_SOFTWARE_UPDATE
2603 // We might want to enable or disable automatic checks here as well.
2604 update_action_->setEnabled(!capture_in_progress);
2605 #endif
2607 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(capture_in_progress);
2609 // XXX Fix packet list heading menu sensitivity
2610 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortAscending",
2611 // !capture_in_progress);
2612 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortDescending",
2613 // !capture_in_progress);
2614 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/NoSorting",
2615 // !capture_in_progress);
2617 #ifdef HAVE_LIBPCAP
2618 main_ui_->actionCaptureOptions->setEnabled(!capture_in_progress);
2619 main_ui_->actionCaptureStart->setEnabled(!capture_in_progress);
2620 main_ui_->actionCaptureStart->setChecked(capture_in_progress);
2621 main_ui_->actionCaptureStop->setEnabled(capture_in_progress);
2622 main_ui_->actionCaptureRestart->setEnabled(capture_in_progress);
2623 main_ui_->actionCaptureRefreshInterfaces->setEnabled(!capture_in_progress);
2624 #endif /* HAVE_LIBPCAP */
2628 void WiresharkMainWindow::setMenusForCaptureStopping() {
2629 main_ui_->actionFileQuit->setEnabled(false);
2630 #ifdef HAVE_SOFTWARE_UPDATE
2631 update_action_->setEnabled(false);
2632 #endif
2633 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(false);
2634 #ifdef HAVE_LIBPCAP
2635 main_ui_->actionCaptureStart->setChecked(false);
2636 main_ui_->actionCaptureStop->setEnabled(false);
2637 main_ui_->actionCaptureRestart->setEnabled(false);
2638 #endif /* HAVE_LIBPCAP */
2641 void WiresharkMainWindow::setForCapturedPackets(bool have_captured_packets)
2643 main_ui_->actionFilePrint->setEnabled(have_captured_packets);
2645 // set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/Print",
2646 // have_captured_packets);
2648 main_ui_->actionEditFindPacket->setEnabled(have_captured_packets);
2649 main_ui_->actionEditFindNext->setEnabled(have_captured_packets);
2650 main_ui_->actionEditFindPrevious->setEnabled(have_captured_packets);
2652 main_ui_->actionGoGoToPacket->setEnabled(have_captured_packets);
2653 main_ui_->actionGoPreviousPacket->setEnabled(have_captured_packets);
2654 main_ui_->actionGoNextPacket->setEnabled(have_captured_packets);
2655 main_ui_->actionGoFirstPacket->setEnabled(have_captured_packets);
2656 main_ui_->actionGoLastPacket->setEnabled(have_captured_packets);
2657 main_ui_->actionGoNextConversationPacket->setEnabled(have_captured_packets);
2658 main_ui_->actionGoPreviousConversationPacket->setEnabled(have_captured_packets);
2660 main_ui_->actionViewZoomIn->setEnabled(have_captured_packets);
2661 main_ui_->actionViewZoomOut->setEnabled(have_captured_packets);
2662 main_ui_->actionViewNormalSize->setEnabled(have_captured_packets);
2663 main_ui_->actionViewResizeColumns->setEnabled(have_captured_packets);
2665 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(have_captured_packets);
2666 main_ui_->actionStatisticsProtocolHierarchy->setEnabled(have_captured_packets);
2667 main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets);
2670 void WiresharkMainWindow::setMenusForFileSet(bool enable_list_files) {
2671 bool enable_next = fileset_get_next() != NULL && enable_list_files;
2672 bool enable_prev = fileset_get_previous() != NULL && enable_list_files;
2674 main_ui_->actionFileSetListFiles->setEnabled(enable_list_files);
2675 main_ui_->actionFileSetNextFile->setEnabled(enable_next);
2676 main_ui_->actionFileSetPreviousFile->setEnabled(enable_prev);
2679 void WiresharkMainWindow::setWindowIcon(const QIcon &icon) {
2680 mainApp->setWindowIcon(icon);
2681 QMainWindow::setWindowIcon(icon);
2684 void WiresharkMainWindow::updateForUnsavedChanges() {
2685 updateTitlebar();
2686 setMenusForCaptureFile();
2689 void WiresharkMainWindow::changeEvent(QEvent* event)
2691 if (0 != event)
2693 switch (event->type())
2695 case QEvent::LanguageChange:
2696 main_ui_->retranslateUi(this);
2697 // make sure that the "Clear Menu" item is retranslated
2698 mainApp->emitAppSignal(WiresharkApplication::RecentCapturesChanged);
2699 // make sure that the color actions in the PacketList and ProtoTree
2700 // are retranslated
2701 initConversationMenus();
2702 updateTitlebar();
2703 break;
2704 case QEvent::LocaleChange: {
2705 QString locale = QLocale::system().name();
2706 locale.truncate(locale.lastIndexOf('_'));
2707 mainApp->loadLanguage(locale);
2709 break;
2710 case QEvent::WindowStateChange:
2711 main_ui_->actionViewFullScreen->setChecked(this->isFullScreen());
2712 break;
2713 default:
2714 break;
2717 QMainWindow::changeEvent(event);
2720 /* Update main window items based on whether there's a capture in progress. */
2721 void WiresharkMainWindow::setForCaptureInProgress(bool capture_in_progress, bool handle_toolbars, GArray *ifaces)
2723 setMenusForCaptureInProgress(capture_in_progress);
2725 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
2726 wireless_frame_->setCaptureInProgress(capture_in_progress);
2727 #endif
2729 #ifdef HAVE_LIBPCAP
2730 packet_list_->setCaptureInProgress(capture_in_progress, main_ui_->actionGoAutoScroll->isChecked());
2732 // set_capture_if_dialog_for_capture_in_progress(capture_in_progress);
2733 #endif
2735 if (handle_toolbars) {
2736 QList<InterfaceToolbar *> toolbars = findChildren<InterfaceToolbar *>();
2737 foreach(InterfaceToolbar *toolbar, toolbars) {
2738 if (capture_in_progress) {
2739 toolbar->startCapture(ifaces);
2740 } else {
2741 toolbar->stopCapture();
2747 void WiresharkMainWindow::addMenuActions(QList<QAction *> &actions, int menu_group)
2749 foreach(QAction *action, actions) {
2750 switch (menu_group) {
2751 case REGISTER_PACKET_ANALYZE_GROUP_UNSORTED:
2752 case REGISTER_PACKET_STAT_GROUP_UNSORTED:
2753 case REGISTER_STAT_GROUP_GENERIC:
2754 // XXX - The Lua documentation claims that ANALYZE_GROUP_UNSORTED
2755 // is under the Analyze menu, and STAT_GROUP_GENERIC and
2756 // PACKET_STAT_GROUP_UNSORTED are distinguished by whether they
2757 // go before the separator in the group of non protocol-specific
2758 // actions or after the separator with the protocol-specific
2759 // actions. We currently put them all in the same place.
2760 main_ui_->menuStatistics->insertAction(
2761 main_ui_->actionStatistics_REGISTER_STAT_GROUP_UNSORTED,
2762 action);
2763 break;
2764 case REGISTER_STAT_GROUP_RESPONSE_TIME:
2765 main_ui_->menuServiceResponseTime->addAction(action);
2766 break;
2767 case REGISTER_STAT_GROUP_RSERPOOL:
2768 main_ui_->menuRSerPool->addAction(action);
2769 break;
2770 case REGISTER_TELEPHONY_GROUP_UNSORTED:
2771 main_ui_->menuTelephony->addAction(action);
2772 break;
2773 case REGISTER_TELEPHONY_GROUP_ANSI:
2774 main_ui_->menuANSI->addAction(action);
2775 break;
2776 case REGISTER_TELEPHONY_GROUP_GSM:
2777 main_ui_->menuGSM->addAction(action);
2778 break;
2779 case REGISTER_TELEPHONY_GROUP_3GPP_UU:
2780 main_ui_->menuLTE->addAction(action);
2781 break;
2782 case REGISTER_TELEPHONY_GROUP_MTP3:
2783 main_ui_->menuMTP3->addAction(action);
2784 break;
2785 case REGISTER_TELEPHONY_GROUP_SCTP:
2786 // XXX - There are two SCTP menus, under Analyze and Telephony,
2787 // that have the same default actions. The default actions from
2788 // Analyze are copied to the PacketList context menu.
2789 main_ui_->menuTelephonySCTP->addAction(action);
2790 break;
2791 case REGISTER_TOOLS_GROUP_UNSORTED:
2793 // Allow the creation of submenus. Mimics the behavior of
2794 // ui/gtk/main_menubar.c:add_menu_item_to_main_menubar
2795 // and GtkUIManager.
2797 // For now we limit the insanity to the "Tools" menu.
2798 QStringList menu_path = action->text().split('/');
2799 QMenu *cur_menu = main_ui_->menuTools;
2800 while (menu_path.length() > 1) {
2801 QString menu_title = menu_path.takeFirst();
2802 QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
2803 if (!submenu) {
2804 submenu = cur_menu->addMenu(menu_title);
2805 submenu->setObjectName(menu_title.toLower());
2807 cur_menu = submenu;
2809 action->setText(menu_path.last());
2810 cur_menu->addAction(action);
2811 break;
2813 default:
2814 // Skip log items.
2815 return;
2818 // Connect each action type to its corresponding slot. We to
2819 // distinguish various types of actions. Setting their objectName
2820 // seems to work OK.
2821 if (action->objectName() == TapParameterDialog::actionName()) {
2822 connect(action, &QAction::triggered, this, [=]() { openTapParameterDialog(); });
2823 } else if (action->objectName() == FunnelStatistics::actionName()) {
2824 connect(action, &QAction::triggered, funnel_statistics_, &FunnelStatistics::funnelActionTriggered);
2828 void WiresharkMainWindow::removeMenuActions(QList<QAction *> &actions, int menu_group)
2830 foreach(QAction *action, actions) {
2831 switch (menu_group) {
2832 case REGISTER_PACKET_ANALYZE_GROUP_UNSORTED:
2833 case REGISTER_PACKET_STAT_GROUP_UNSORTED:
2834 case REGISTER_STAT_GROUP_GENERIC:
2835 main_ui_->menuStatistics->removeAction(action);
2836 break;
2837 case REGISTER_STAT_GROUP_RESPONSE_TIME:
2838 main_ui_->menuServiceResponseTime->removeAction(action);
2839 break;
2840 case REGISTER_STAT_GROUP_RSERPOOL:
2841 main_ui_->menuRSerPool->removeAction(action);
2842 break;
2843 case REGISTER_TELEPHONY_GROUP_UNSORTED:
2844 main_ui_->menuTelephony->removeAction(action);
2845 break;
2846 case REGISTER_TELEPHONY_GROUP_ANSI:
2847 main_ui_->menuANSI->removeAction(action);
2848 break;
2849 case REGISTER_TELEPHONY_GROUP_GSM:
2850 main_ui_->menuGSM->removeAction(action);
2851 break;
2852 case REGISTER_TELEPHONY_GROUP_3GPP_UU:
2853 main_ui_->menuLTE->removeAction(action);
2854 break;
2855 case REGISTER_TELEPHONY_GROUP_MTP3:
2856 main_ui_->menuMTP3->removeAction(action);
2857 break;
2858 case REGISTER_TELEPHONY_GROUP_SCTP:
2859 main_ui_->menuTelephonySCTP->removeAction(action);
2860 break;
2861 case REGISTER_TOOLS_GROUP_UNSORTED:
2863 // Allow removal of submenus.
2864 // For now we limit the insanity to the "Tools" menu.
2865 QStringList menu_path = action->text().split('/');
2866 QMenu *cur_menu = main_ui_->menuTools;
2867 while (menu_path.length() > 1) {
2868 QString menu_title = menu_path.takeFirst();
2869 QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
2870 cur_menu = submenu;
2872 cur_menu->removeAction(action);
2873 // Remove empty submenus.
2874 while (cur_menu != main_ui_->menuTools) {
2875 QMenu *empty_menu = (cur_menu->isEmpty() ? cur_menu : NULL);
2876 cur_menu = dynamic_cast<QMenu *>(cur_menu->parent());
2877 delete empty_menu;
2879 break;
2881 default:
2882 // qDebug() << "FIX: Remove" << action->text() << "from the menu";
2883 break;
2888 void WiresharkMainWindow::addDynamicMenus()
2890 // Manual additions
2891 mainApp->addDynamicMenuGroupItem(REGISTER_TELEPHONY_GROUP_GSM, main_ui_->actionTelephonyGsmMapSummary);
2892 mainApp->addDynamicMenuGroupItem(REGISTER_TELEPHONY_GROUP_3GPP_UU, main_ui_->actionTelephonyLteMacStatistics);
2893 mainApp->addDynamicMenuGroupItem(REGISTER_TELEPHONY_GROUP_3GPP_UU, main_ui_->actionTelephonyLteRlcStatistics);
2894 mainApp->addDynamicMenuGroupItem(REGISTER_TELEPHONY_GROUP_3GPP_UU, main_ui_->actionTelephonyLteRlcGraph);
2895 mainApp->addDynamicMenuGroupItem(REGISTER_TELEPHONY_GROUP_MTP3, main_ui_->actionTelephonyMtp3Summary);
2896 mainApp->addDynamicMenuGroupItem(REGISTER_TELEPHONY_GROUP_UNSORTED, main_ui_->actionTelephonySipFlows);
2898 // Fill in each menu
2899 foreach(register_stat_group_t menu_group, menu_groups_) {
2900 QList<QAction *>actions = mainApp->dynamicMenuGroupItems(menu_group);
2901 addMenuActions(actions, menu_group);
2904 // Empty menus don't show up: https://bugreports.qt.io/browse/QTBUG-33728
2905 // We've added a placeholder in order to make sure some menus are visible.
2906 // Hide them as needed.
2907 if (mainApp->dynamicMenuGroupItems(REGISTER_TELEPHONY_GROUP_ANSI).length() > 0) {
2908 main_ui_->actionTelephonyANSIPlaceholder->setVisible(false);
2910 if (mainApp->dynamicMenuGroupItems(REGISTER_TELEPHONY_GROUP_GSM).length() > 0) {
2911 main_ui_->actionTelephonyGSMPlaceholder->setVisible(false);
2913 if (mainApp->dynamicMenuGroupItems(REGISTER_TELEPHONY_GROUP_3GPP_UU).length() > 0) {
2914 main_ui_->actionTelephonyLTEPlaceholder->setVisible(false);
2916 if (mainApp->dynamicMenuGroupItems(REGISTER_TELEPHONY_GROUP_MTP3).length() > 0) {
2917 main_ui_->actionTelephonyMTP3Placeholder->setVisible(false);
2921 void WiresharkMainWindow::reloadDynamicMenus()
2923 foreach(register_stat_group_t menu_group, menu_groups_) {
2924 QList<QAction *>actions = mainApp->removedMenuGroupItems(menu_group);
2925 removeMenuActions(actions, menu_group);
2927 actions = mainApp->addedMenuGroupItems(menu_group);
2928 addMenuActions(actions, menu_group);
2931 mainApp->clearAddedMenuGroupItems();
2932 mainApp->clearRemovedMenuGroupItems();
2935 void WiresharkMainWindow::externalMenuHelper(ext_menu_t * menu, QMenu * subMenu, int depth)
2937 QAction * itemAction = Q_NULLPTR;
2938 ext_menubar_t * item = Q_NULLPTR;
2939 GList * children = Q_NULLPTR;
2941 /* There must exists an xpath parent */
2942 Q_ASSERT(subMenu != NULL);
2944 /* If the depth counter exceeds, something must have gone wrong */
2945 Q_ASSERT(depth < EXT_MENUBAR_MAX_DEPTH);
2947 children = menu->children;
2948 /* Iterate the child entries */
2949 while (children && children->data) {
2950 item = gxx_list_data(ext_menubar_t *, children);
2952 if (item->type == EXT_MENUBAR_MENU) {
2953 /* Handle Submenu entry */
2954 this->externalMenuHelper(item, subMenu->addMenu(item->label), depth++);
2955 } else if (item->type == EXT_MENUBAR_SEPARATOR) {
2956 subMenu->addSeparator();
2957 } else if (item->type == EXT_MENUBAR_ITEM || item->type == EXT_MENUBAR_URL) {
2958 itemAction = subMenu->addAction(item->name);
2959 itemAction->setData(QVariant::fromValue(static_cast<void *>(item)));
2960 itemAction->setText(item->label);
2961 connect(itemAction, &QAction::triggered, this, &WiresharkMainWindow::externalMenuItemTriggered);
2964 /* Iterate Loop */
2965 children = gxx_list_next(children);
2969 QMenu * WiresharkMainWindow::searchSubMenu(QString objectName)
2971 QList<QMenu*> lst;
2973 if (objectName.length() > 0) {
2974 QString searchName = QStringLiteral("menu") + objectName;
2976 lst = main_ui_->menuBar->findChildren<QMenu*>();
2977 foreach(QMenu* m, lst) {
2978 if (QString::compare(m->objectName(), searchName) == 0)
2979 return m;
2983 return 0;
2986 void WiresharkMainWindow::addPluginIFStructures()
2988 GList *user_menu = ext_menubar_get_entries();
2990 while (user_menu && user_menu->data) {
2991 QMenu *subMenu = Q_NULLPTR;
2992 ext_menu_t *menu = gxx_list_data(ext_menu_t *, user_menu);
2994 /* On this level only menu items should exist. Not doing an assert here,
2995 * as it could be an honest mistake */
2996 if (menu->type != EXT_MENUBAR_MENU) {
2997 user_menu = gxx_list_next(user_menu);
2998 continue;
3001 /* Create main submenu and add it to the menubar */
3002 if (menu->parent_menu) {
3003 QMenu *sortUnderneath = searchSubMenu(QString(menu->parent_menu));
3004 if (sortUnderneath)
3005 subMenu = sortUnderneath->addMenu(menu->label);
3008 if (!subMenu)
3009 subMenu = main_ui_->menuBar->addMenu(menu->label);
3011 /* This will generate the action structure for each menu. It is recursive,
3012 * therefore a sub-routine, and we have a depth counter to prevent endless loops. */
3013 this->externalMenuHelper(menu, subMenu, 0);
3015 /* Iterate Loop */
3016 user_menu = gxx_list_next(user_menu);
3019 int cntToolbars = 0;
3021 QMenu *tbMenu = main_ui_->menuAdditionalToolbars;
3022 GList *if_toolbars = ext_toolbar_get_entries();
3023 while (if_toolbars && if_toolbars->data) {
3024 ext_toolbar_t *toolbar = gxx_list_data(ext_toolbar_t*, if_toolbars);
3026 if (toolbar->type != EXT_TOOLBAR_BAR) {
3027 if_toolbars = gxx_list_next(if_toolbars);
3028 continue;
3031 bool visible = g_list_find_custom(recent.gui_additional_toolbars, toolbar->name, reinterpret_cast<GCompareFunc>(strcmp)) ? true : false;
3033 AdditionalToolBar *ifToolBar = AdditionalToolBar::create(this, toolbar);
3035 if (ifToolBar) {
3036 ifToolBar->setVisible(visible);
3038 QAction *iftbAction = new QAction(QString(toolbar->name), this);
3039 iftbAction->setToolTip(toolbar->tooltip);
3040 iftbAction->setEnabled(true);
3041 iftbAction->setCheckable(true);
3042 iftbAction->setChecked(visible);
3043 iftbAction->setToolTip(tr("Show or hide the toolbar"));
3044 iftbAction->setData(VariantPointer<ext_toolbar_t>::asQVariant(toolbar));
3046 QAction *before = Q_NULLPTR;
3048 foreach(QAction *action, tbMenu->actions()) {
3049 /* Ensure we add the menu entries in sorted order */
3050 if (action->text().compare(toolbar->name, Qt::CaseInsensitive) > 0) {
3051 before = action;
3052 break;
3056 tbMenu->insertAction(before, iftbAction);
3058 addToolBar(Qt::TopToolBarArea, ifToolBar);
3059 insertToolBarBreak(ifToolBar);
3061 if (show_hide_actions_)
3062 show_hide_actions_->addAction(iftbAction);
3064 cntToolbars++;
3067 if_toolbars = gxx_list_next(if_toolbars);
3070 if (cntToolbars)
3071 tbMenu->menuAction()->setVisible(true);
3074 void WiresharkMainWindow::removeAdditionalToolbar(QString toolbarName)
3076 if (toolbarName.length() == 0)
3077 return;
3079 QList<QToolBar *> toolbars = findChildren<QToolBar *>();
3080 foreach(QToolBar *tb, toolbars) {
3081 AdditionalToolBar *ifToolBar = dynamic_cast<AdditionalToolBar *>(tb);
3083 if (ifToolBar && ifToolBar->menuName().compare(toolbarName)) {
3084 GList *entry = g_list_find_custom(recent.gui_additional_toolbars, qUtf8Printable(ifToolBar->menuName()), reinterpret_cast<GCompareFunc>(strcmp));
3085 if (entry) {
3086 recent.gui_additional_toolbars = g_list_remove(recent.gui_additional_toolbars, entry->data);
3088 QList<QAction *> actions = main_ui_->menuAdditionalToolbars->actions();
3089 foreach(QAction *action, actions) {
3090 ext_toolbar_t *item = VariantPointer<ext_toolbar_t>::asPtr(action->data());
3091 if (item && ifToolBar->menuName().compare(item->name)) {
3092 if (show_hide_actions_)
3093 show_hide_actions_->removeAction(action);
3094 main_ui_->menuAdditionalToolbars->removeAction(action);
3097 break;
3103 QString WiresharkMainWindow::getMwFileName()
3105 return mwFileName_;
3108 void WiresharkMainWindow::setMwFileName(QString fileName)
3110 mwFileName_ = fileName;
3111 return;
3114 // Finds rtp id for selected stream and adds it to stream_ids
3115 // If reverse is set, tries to find reverse stream and other streams
3116 // bundled on the same RTP session too
3117 // Return error string if error happens
3119 // Note: Caller must free each returned rtpstream_info_t
3120 QString WiresharkMainWindow::findRtpStreams(QVector<rtpstream_id_t *> *stream_ids, bool reverse)
3122 rtpstream_tapinfo_t tapinfo;
3123 rtpstream_id_t *new_id;
3124 const char filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)";
3125 dfilter_t *sfcode;
3126 df_error_t *df_err = NULL;
3128 /* Try to get the hfid for "rtp.ssrc". */
3129 int hfid_rtp_ssrc = proto_registrar_get_id_byname("rtp.ssrc");
3130 if (hfid_rtp_ssrc == -1) {
3131 return tr("There is no \"rtp.ssrc\" field in this version of Wireshark.");
3134 /* Try to compile the filter. */
3135 if (!dfilter_compile(filter_text, &sfcode, &df_err)) {
3136 QString err = QString(df_err->msg);
3137 df_error_free(&df_err);
3138 return err;
3141 if (!capture_file_.capFile() || !capture_file_.capFile()->current_frame) close();
3143 if (!cf_read_current_record(capture_file_.capFile())) close();
3145 frame_data *fdata = capture_file_.capFile()->current_frame;
3147 epan_dissect_t edt;
3149 epan_dissect_init(&edt, capture_file_.capFile()->epan, true, false);
3150 epan_dissect_prime_with_dfilter(&edt, sfcode);
3151 epan_dissect_prime_with_hfid(&edt, hfid_rtp_ssrc);
3152 epan_dissect_run(&edt, capture_file_.capFile()->cd_t,
3153 &capture_file_.capFile()->rec,
3154 frame_tvbuff_new_buffer(
3155 &capture_file_.capFile()->provider, fdata,
3156 &capture_file_.capFile()->buf),
3157 fdata, NULL);
3160 * Packet must be an RTPv2 packet with an SSRC; we use the filter to
3161 * check.
3163 if (!dfilter_apply_edt(sfcode, &edt)) {
3164 epan_dissect_cleanup(&edt);
3165 dfilter_free(sfcode);
3166 return tr("Please select an RTPv2 packet with an SSRC value");
3169 dfilter_free(sfcode);
3171 if (!reverse) {
3172 // If we only want streams that match the SSRC in this frame, we
3173 // can just allocate an RTP stream ID directly instead of having
3174 // to redissect all the other packets.
3176 /* We need the SSRC value of the current frame; try to get it. */
3177 GPtrArray *gp = proto_get_finfo_ptr_array(edt.tree, hfid_rtp_ssrc);
3178 if (gp == NULL || gp->len == 0) {
3179 /* XXX - should not happen, as the filter includes rtp.ssrc */
3180 epan_dissect_cleanup(&edt);
3181 return tr("SSRC value not found.");
3185 * OK, we have the SSRC value(s), so we can proceed.
3186 * (Try to handle the unlikely case of a frame with more than one
3187 * SSRC; perhaps a DVD-S2 Baseband frame? Does that even work
3188 * properly?)
3190 for (unsigned i = 0; i < gp->len; i++) {
3191 new_id = g_new0(rtpstream_id_t, 1);
3192 rtpstream_id_copy_pinfo(&(edt.pi), new_id, false);
3193 new_id->ssrc = fvalue_get_uinteger(((field_info *)gp->pdata[i])->value);
3194 *stream_ids << new_id;
3196 } else {
3197 // If we want to find all SSRCs with the same RTP session as this
3198 // frame, then we have to redissect all packets.
3200 /* Register the tap listener */
3201 memset(&tapinfo, 0, sizeof(rtpstream_tapinfo_t));
3202 tapinfo.tap_data = this;
3203 tapinfo.mode = TAP_ANALYSE;
3205 /* Scan for RTP streams (redissect all packets) */
3206 rtpstream_scan(&tapinfo, capture_file_.capFile(), Q_NULLPTR);
3208 for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = gxx_list_next(strinfo_list)) {
3209 rtpstream_info_t * strinfo = gxx_list_data(rtpstream_info_t*, strinfo_list);
3210 // We want any RTP stream ID that matches the address and ports.
3211 // This could mean more than one in the forward direction, if
3212 // e.g., BUNDLE is used (RFC 9143).
3213 if (rtpstream_id_equal_pinfo(&(strinfo->id), &(edt.pi), false) ||
3214 rtpstream_id_equal_pinfo(&(strinfo->id), &(edt.pi), true)) {
3215 new_id = g_new0(rtpstream_id_t, 1);
3216 rtpstream_id_copy(&(strinfo->id), new_id);
3217 *stream_ids << new_id;
3220 rtpstream_reset_cb(&tapinfo);
3223 epan_dissect_cleanup(&edt);
3225 return NULL;
3228 void WiresharkMainWindow::openTLSKeylogDialog()
3230 // Have a single instance of the dialog at any one time.
3231 if (!tlskeylog_dialog_) {
3232 tlskeylog_dialog_ = new TLSKeylogDialog(*this);
3233 tlskeylog_dialog_->setAttribute(Qt::WA_DeleteOnClose);
3236 if (tlskeylog_dialog_->isMinimized()) {
3237 tlskeylog_dialog_->showNormal();
3239 else {
3240 tlskeylog_dialog_->show();
3242 tlskeylog_dialog_->raise();
3243 tlskeylog_dialog_->activateWindow();