1 /* stratoshark_main_window.cpp
3 * Stratoshark - System call and event log analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
10 #include "main_application.h"
11 #include "stratoshark_main_window.h"
14 * The generated Ui_StratosharkMainWindow::setupUi() can grow larger than our configured limit,
15 * so turn off -Wframe-larger-than= for ui_stratoshark_main_window.h.
17 DIAG_OFF(frame
-larger
-than
=)
18 #include <ui_stratoshark_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/plugin_if.h>
30 #include <frame_tvbuff.h>
32 #include "ui/iface_toolbar.h"
33 #include "ui/commandline.h"
36 #include "ui/capture.h"
37 #include <capture/capture_session.h>
40 #include "ui/alert_box.h"
42 #include "ui/capture_ui_utils.h"
44 #include "ui/capture_globals.h"
45 #include "ui/main_statusbar.h"
46 #include "ui/recent.h"
47 #include "ui/recent_utils.h"
49 #include "ui/preference_utils.h"
51 #include "byte_view_tab.h"
53 #include "capture_options_dialog.h"
55 #include "conversation_colorize_action.h"
56 #include "export_dissection_dialog.h"
57 #include "file_set_dialog.h"
58 #include "filter_dialog.h"
59 #include "follow_stream_action.h"
60 #include "funnel_statistics.h"
61 #include "import_text_dialog.h"
62 #include "interface_toolbar.h"
63 #include "packet_list.h"
64 #include "proto_tree.h"
65 #include "simple_dialog.h"
66 #include "tap_parameter_dialog.h"
68 #include <ui/qt/widgets/additional_toolbar.h>
69 #include <ui/qt/widgets/display_filter_edit.h>
70 #include <ui/qt/widgets/filter_expression_toolbar.h>
72 #include <ui/qt/utils/color_utils.h>
73 #include <ui/qt/utils/profile_switcher.h>
74 #include <ui/qt/utils/qt_ui_utils.h>
75 #include <ui/qt/utils/stock_icon.h>
76 #include <ui/qt/utils/variant_pointer.h>
79 #include <QActionGroup>
80 #include <QIntValidator>
83 #include <QMessageBox>
84 #include <QMetaObject>
88 #include <QToolButton>
89 #include <QTreeWidget>
92 //menu_recent_file_write_all
94 // If we ever add support for multiple windows this will need to be replaced.
95 static StratosharkMainWindow
*gbl_cur_main_window_
;
97 static void plugin_if_mainwindow_apply_filter(GHashTable
* data_set
)
99 if (!gbl_cur_main_window_
|| !data_set
)
102 if (g_hash_table_lookup_extended(data_set
, "filter_string", NULL
, NULL
)) {
103 QString
filter((const char *)g_hash_table_lookup(data_set
, "filter_string"));
104 gbl_cur_main_window_
->filterPackets(filter
);
108 static void plugin_if_mainwindow_preference(GHashTable
* data_set
)
110 if (!gbl_cur_main_window_
|| !data_set
)
113 const char * module_name
;
114 const char * pref_name
;
115 const char * pref_value
;
117 DIAG_OFF_CAST_AWAY_CONST
118 if (g_hash_table_lookup_extended(data_set
, "pref_module", NULL
, (void * *)&module_name
) &&
119 g_hash_table_lookup_extended(data_set
, "pref_key", NULL
, (void * *)&pref_name
) &&
120 g_hash_table_lookup_extended(data_set
, "pref_value", NULL
, (void * *)&pref_value
))
122 unsigned int changed_flags
= prefs_store_ext(module_name
, pref_name
, pref_value
);
124 mainApp
->emitAppSignal(WiresharkApplication::PacketDissectionChanged
);
125 mainApp
->emitAppSignal(WiresharkApplication::PreferencesChanged
);
128 DIAG_ON_CAST_AWAY_CONST
131 static void plugin_if_mainwindow_gotoframe(GHashTable
* data_set
)
133 if (!gbl_cur_main_window_
|| !data_set
)
138 if (g_hash_table_lookup_extended(data_set
, "frame_nr", NULL
, &framenr
)) {
139 if (GPOINTER_TO_UINT(framenr
) != 0)
140 gbl_cur_main_window_
->gotoFrame(GPOINTER_TO_UINT(framenr
));
146 static void plugin_if_mainwindow_get_ws_info(GHashTable
* data_set
)
148 if (!gbl_cur_main_window_
|| !data_set
)
151 ws_info_t
*ws_info
= NULL
;
153 if (!g_hash_table_lookup_extended(data_set
, "ws_info", NULL
, (void**)&ws_info
))
156 CaptureFile
*cfWrap
= gbl_cur_main_window_
->captureFile();
157 capture_file
*cf
= cfWrap
->capFile();
159 ws_info
->ws_info_supported
= true;
161 /* If we have a filename attached to ws_info clear it */
162 if (ws_info
->cf_filename
!= NULL
)
164 g_free(ws_info
->cf_filename
);
165 ws_info
->cf_filename
= NULL
;
168 /* Determine the true state of the capture file. We return the true state in
169 the ws_info structure and DON'T CHANGE the cf->state as we don't want to cause problems
170 with code that follows this. */
175 /* As we have a cf->filename we'll use the name and the state */
176 ws_info
->cf_filename
= g_strdup(cf
->filename
);
177 ws_info
->cf_state
= cf
->state
;
181 /* When we come through here the cf->state can show FILE_READ_DONE even though the
182 file is actually closed (no filename). A better fix would be to have a
183 FILE_CLOSE_PENDING state but that involves a lot of code change elsewhere. */
184 ws_info
->cf_state
= FILE_CLOSED
;
188 if (!ws_info
->cf_filename
)
190 /* We may have a filename associated with the main window so let's use it */
191 QString fileNameString
= gbl_cur_main_window_
->getMwFileName();
192 if (fileNameString
.length())
194 QByteArray ba
= fileNameString
.toLatin1();
195 const char *c_file_name
= ba
.data();
196 ws_info
->cf_filename
= g_strdup(c_file_name
);
201 ws_info
->cf_count
= cf
->count
;
203 QList
<int> rows
= gbl_cur_main_window_
->selectedRows();
204 frame_data
* fdata
= NULL
;
205 if (rows
.count() > 0)
206 fdata
= gbl_cur_main_window_
->frameDataForRow(rows
.at(0));
208 if (cf
->state
== FILE_READ_DONE
&& fdata
) {
209 ws_info
->cf_framenr
= fdata
->num
;
210 ws_info
->frame_passed_dfilter
= (fdata
->passed_dfilter
== 1);
213 ws_info
->cf_framenr
= 0;
214 ws_info
->frame_passed_dfilter
= false;
219 /* Initialise the other ws_info structure values */
220 ws_info
->cf_count
= 0;
221 ws_info
->cf_framenr
= 0;
222 ws_info
->frame_passed_dfilter
= false;
226 #endif /* HAVE_LIBPCAP */
228 static void plugin_if_mainwindow_get_frame_data(GHashTable
* data_set
)
230 if (!gbl_cur_main_window_
|| !data_set
)
233 plugin_if_frame_data_cb extract_cb
;
235 void** ret_value_ptr
;
237 if (g_hash_table_lookup_extended(data_set
, "extract_cb", NULL
, (void**)&extract_cb
) &&
238 g_hash_table_lookup_extended(data_set
, "user_data", NULL
, (void**)&user_data
) &&
239 g_hash_table_lookup_extended(data_set
, "ret_value_ptr", NULL
, (void**)&ret_value_ptr
))
241 QList
<int> rows
= gbl_cur_main_window_
->selectedRows();
242 if (rows
.count() > 0) {
243 frame_data
* fdata
= gbl_cur_main_window_
->frameDataForRow(rows
.at(0));
245 *ret_value_ptr
= extract_cb(fdata
, user_data
);
251 static void plugin_if_mainwindow_get_capture_file(GHashTable
* data_set
)
253 if (!gbl_cur_main_window_
|| !data_set
)
256 plugin_if_capture_file_cb extract_cb
;
258 void** ret_value_ptr
;
260 if (g_hash_table_lookup_extended(data_set
, "extract_cb", NULL
, (void**)&extract_cb
) &&
261 g_hash_table_lookup_extended(data_set
, "user_data", NULL
, (void**)&user_data
) &&
262 g_hash_table_lookup_extended(data_set
, "ret_value_ptr", NULL
, (void**)&ret_value_ptr
))
264 CaptureFile
* cfWrap
= gbl_cur_main_window_
->captureFile();
265 capture_file
* cf
= cfWrap
->capFile();
267 *ret_value_ptr
= extract_cb(cf
, user_data
);
272 static void plugin_if_mainwindow_update_toolbars(GHashTable
* data_set
)
274 if (!gbl_cur_main_window_
|| !data_set
)
277 if (g_hash_table_lookup_extended(data_set
, "toolbar_name", NULL
, NULL
)) {
278 QString
toolbarName((const char *)g_hash_table_lookup(data_set
, "toolbar_name"));
279 gbl_cur_main_window_
->removeAdditionalToolbar(toolbarName
);
284 static void mainwindow_add_toolbar(const iface_toolbar
*toolbar_entry
)
286 if (gbl_cur_main_window_
&& toolbar_entry
)
288 gbl_cur_main_window_
->addInterfaceToolbar(toolbar_entry
);
292 static void mainwindow_remove_toolbar(const char *menu_title
)
294 if (gbl_cur_main_window_
&& menu_title
)
296 gbl_cur_main_window_
->removeInterfaceToolbar(menu_title
);
300 QMenu
* StratosharkMainWindow::findOrAddMenu(QMenu
*parent_menu
, const QStringList
& menu_parts
) {
301 for (auto const & menu_text
: menu_parts
) {
303 for (auto const & action
: parent_menu
->actions()) {
304 if (action
->text() == menu_text
.trimmed()) {
305 parent_menu
= action
->menu();
311 // If we get here the menu entry was not found, add a sub menu
312 parent_menu
= parent_menu
->addMenu(menu_text
.trimmed());
318 StratosharkMainWindow::StratosharkMainWindow(QWidget
*parent
) :
320 main_ui_(new Ui::StratosharkMainWindow
),
321 previous_focus_(NULL
),
322 file_set_dialog_(NULL
),
323 show_hide_actions_(NULL
),
324 time_display_actions_(NULL
),
325 time_precision_actions_(NULL
),
326 funnel_statistics_(NULL
),
328 was_maximized_(false),
329 capture_stopping_(false),
330 capture_filter_valid_(false),
331 use_capturing_title_(false)
333 , capture_options_dialog_(NULL
)
336 #if defined(Q_OS_MAC)
340 if (!gbl_cur_main_window_
) {
341 connect(mainApp
, &MainApplication::openStatCommandDialog
, this, &StratosharkMainWindow::openStatCommandDialog
);
342 connect(mainApp
, &MainApplication::openTapParameterDialog
,
343 this, [=](const QString cfg_str
, const QString arg
, void *userdata
) {openTapParameterDialog(cfg_str
, arg
, userdata
);});
345 gbl_cur_main_window_
= this;
347 capture_input_init(&cap_session_
, CaptureFile::globalCapFile());
351 // setpUi calls QMetaObject::connectSlotsByName(this). connectSlotsByName
352 // iterates over *all* of our children, looking for matching "on_" slots.
353 // The fewer children we have at this point the better.
354 main_ui_
->setupUi(this);
355 #ifdef HAVE_SOFTWARE_UPDATE
356 update_action_
= new QAction(tr("Check for Updates…"), main_ui_
->menuHelp
);
359 menu_groups_
= QList
<register_stat_group_t
>()
360 << REGISTER_LOG_ANALYZE_GROUP_UNSORTED
361 << REGISTER_LOG_STAT_GROUP_UNSORTED
;
363 setWindowIcon(mainApp
->normalIcon());
365 setMenusForCaptureFile();
366 setForCapturedPackets(false);
367 setMenusForFileSet(false);
368 interfaceSelectionChanged();
369 loadWindowGeometry();
372 main_ui_
->actionAnalyzeReloadLuaPlugins
->setVisible(false);
375 qRegisterMetaType
<FilterAction::Action
>("FilterAction::Action");
376 qRegisterMetaType
<FilterAction::ActionType
>("FilterAction::ActionType");
377 connect(this, &StratosharkMainWindow::filterAction
, this, &StratosharkMainWindow::queuedFilterAction
, Qt::QueuedConnection
);
379 //To prevent users use features before initialization complete
380 //Otherwise unexpected problems may occur
381 setFeaturesEnabled(false);
382 connect(mainApp
, &MainApplication::appInitialized
, this, [this]() { setFeaturesEnabled(); });
383 connect(mainApp
, &MainApplication::appInitialized
, this, &StratosharkMainWindow::applyGlobalCommandLineOptions
);
384 connect(mainApp
, &MainApplication::appInitialized
, this, &StratosharkMainWindow::zoomText
);
385 connect(mainApp
, &MainApplication::appInitialized
, this, &StratosharkMainWindow::initViewColorizeMenu
);
386 connect(mainApp
, &MainApplication::appInitialized
, this, &StratosharkMainWindow::addStatsPluginsToMenu
);
387 connect(mainApp
, &MainApplication::appInitialized
, this, &StratosharkMainWindow::addDynamicMenus
);
388 connect(mainApp
, &MainApplication::appInitialized
, this, &StratosharkMainWindow::addPluginIFStructures
);
389 connect(mainApp
, &MainApplication::appInitialized
, this, &StratosharkMainWindow::initConversationMenus
);
390 connect(mainApp
, &MainApplication::appInitialized
, this, &StratosharkMainWindow::initFollowStreamMenus
);
391 connect(mainApp
, &MainApplication::appInitialized
, this,
392 [=]() { addDisplayFilterTranslationActions(main_ui_
->menuEditCopy
); });
394 connect(mainApp
, &MainApplication::profileChanging
, this, &StratosharkMainWindow::saveWindowGeometry
);
395 connect(mainApp
, &MainApplication::preferencesChanged
, this, &StratosharkMainWindow::layoutPanes
);
396 connect(mainApp
, &MainApplication::preferencesChanged
, this, &StratosharkMainWindow::layoutToolbars
);
397 connect(mainApp
, &MainApplication::preferencesChanged
, this, &StratosharkMainWindow::updatePreferenceActions
);
398 connect(mainApp
, &MainApplication::preferencesChanged
, this, &StratosharkMainWindow::zoomText
);
399 connect(mainApp
, &MainApplication::preferencesChanged
, this, &StratosharkMainWindow::updateTitlebar
);
401 connect(mainApp
, &MainApplication::updateRecentCaptureStatus
, this, &StratosharkMainWindow::updateRecentCaptures
);
402 connect(mainApp
, &MainApplication::preferencesChanged
, this, &StratosharkMainWindow::updateRecentCaptures
);
403 updateRecentCaptures();
405 #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN)
406 connect(mainApp
, &MainApplication::softwareUpdateRequested
, this, &StratosharkMainWindow::softwareUpdateRequested
,
407 Qt::BlockingQueuedConnection
);
410 df_combo_box_
= new DisplayFilterCombo(this);
412 funnel_statistics_
= new FunnelStatistics(this, capture_file_
);
413 connect(df_combo_box_
, &QComboBox::editTextChanged
, funnel_statistics_
, &FunnelStatistics::displayFilterTextChanged
);
414 connect(funnel_statistics_
, &FunnelStatistics::setDisplayFilter
, this, &StratosharkMainWindow::setDisplayFilter
);
415 connect(funnel_statistics_
, &FunnelStatistics::openCaptureFile
, this,
416 [=](QString cf_path
, QString filter
) { openCaptureFile(cf_path
, filter
); });
418 connect(df_combo_box_
, &QComboBox::editTextChanged
, this, &StratosharkMainWindow::updateDisplayFilterTranslationActions
);
420 file_set_dialog_
= new FileSetDialog(this);
421 connect(file_set_dialog_
, &FileSetDialog::fileSetOpenCaptureFile
, this, [=](QString cf_path
) { openCaptureFile(cf_path
); });
423 initMainToolbarIcons();
425 main_ui_
->displayFilterToolBar
->insertWidget(main_ui_
->actionNewDisplayFilterExpression
, df_combo_box_
);
427 // Make sure filter expressions overflow into a menu instead of a
428 // larger toolbar. We do this by adding them to a child toolbar.
429 // https://bugreports.qt.io/browse/QTBUG-2472
430 FilterExpressionToolBar
*filter_expression_toolbar_
= new FilterExpressionToolBar(this);
431 connect(filter_expression_toolbar_
, &FilterExpressionToolBar::filterPreferences
, this, &StratosharkMainWindow::onFilterPreferences
);
432 connect(filter_expression_toolbar_
, &FilterExpressionToolBar::filterSelected
, this, &StratosharkMainWindow::onFilterSelected
);
433 connect(filter_expression_toolbar_
, &FilterExpressionToolBar::filterEdit
, this, &StratosharkMainWindow::onFilterEdit
);
435 main_ui_
->displayFilterToolBar
->addWidget(filter_expression_toolbar_
);
437 main_ui_
->goToFrame
->hide();
438 connect(main_ui_
->goToFrame
, &AccordionFrame::visibilityChanged
, main_ui_
->actionGoGoToPacket
, &QAction::setChecked
);
440 // XXX For some reason the cursor is drawn funny with an input mask set
441 // https://bugreports.qt-project.org/browse/QTBUG-7174
443 main_ui_
->searchFrame
->hide();
444 connect(main_ui_
->searchFrame
, &SearchFrame::visibilityChanged
, main_ui_
->actionEditFindPacket
, &QAction::setChecked
);
446 main_ui_
->addressEditorFrame
->hide();
447 main_ui_
->columnEditorFrame
->hide();
448 main_ui_
->preferenceEditorFrame
->hide();
449 main_ui_
->filterExpressionFrame
->hide();
452 main_ui_
->menuCapture
->setEnabled(false);
453 main_ui_
->actionCaptureStart
->setEnabled(false);
454 main_ui_
->actionCaptureStop
->setEnabled(false);
455 main_ui_
->actionCaptureRestart
->setEnabled(false);
456 main_ui_
->actionCaptureOptions
->setEnabled(false);
457 main_ui_
->actionCaptureRefreshInterfaces
->setEnabled(false);
460 // Set OS specific shortcuts for fullscreen mode
461 #if defined(Q_OS_MAC)
462 main_ui_
->actionViewFullScreen
->setShortcut(QKeySequence::FullScreen
);
464 main_ui_
->actionViewFullScreen
->setShortcut(QKeySequence(Qt::Key_F11
));
467 #if defined(Q_OS_MAC)
469 main_ui_
->goToPacketLabel
->setAttribute(Qt::WA_MacSmallSize
, true);
470 main_ui_
->goToLineEdit
->setAttribute(Qt::WA_MacSmallSize
, true);
471 main_ui_
->goToGo
->setAttribute(Qt::WA_MacSmallSize
, true);
472 main_ui_
->goToCancel
->setAttribute(Qt::WA_MacSmallSize
, true);
474 main_ui_
->actionEditPreferences
->setMenuRole(QAction::PreferencesRole
);
478 connect(main_ui_
->goToGo
, &QPushButton::pressed
, this, &StratosharkMainWindow::goToGoClicked
);
479 connect(main_ui_
->goToCancel
, &QPushButton::pressed
, this, &StratosharkMainWindow::goToCancelClicked
);
481 // A billion-1 is equivalent to the inputMask 900000000 previously used
482 // Avoid QValidator::Intermediate values by using a top value of all 9's
483 #define MAX_GOTO_LINE 999999999
485 QIntValidator
*goToLineQiv
= new QIntValidator(0,MAX_GOTO_LINE
,this);
486 main_ui_
->goToLineEdit
->setValidator(goToLineQiv
);
488 #ifdef HAVE_SOFTWARE_UPDATE
489 QAction
*update_sep
= main_ui_
->menuHelp
->insertSeparator(main_ui_
->actionHelpAbout
);
490 main_ui_
->menuHelp
->insertAction(update_sep
, update_action_
);
491 connect(update_action_
, &QAction::triggered
, this, &StratosharkMainWindow::checkForUpdates
);
493 master_split_
.setObjectName("splitterMaster");
494 extra_split_
.setObjectName("splitterExtra");
495 master_split_
.setChildrenCollapsible(false);
496 extra_split_
.setChildrenCollapsible(false);
497 main_ui_
->mainStack
->addWidget(&master_split_
);
499 empty_pane_
.setObjectName("emptyPane");
500 empty_pane_
.setVisible(false);
502 packet_list_
= new PacketList(&master_split_
);
503 connect(packet_list_
, &PacketList::framesSelected
, this, &StratosharkMainWindow::setMenusForSelectedPacket
);
504 connect(packet_list_
, &PacketList::framesSelected
, this, &StratosharkMainWindow::framesSelected
);
506 QAction
*action
= main_ui_
->menuPacketComment
->addAction(tr("Add New Comment…"));
507 connect(action
, &QAction::triggered
, this, &StratosharkMainWindow::addPacketComment
);
508 action
->setShortcut(QKeySequence(Qt::CTRL
| Qt::ALT
| Qt::Key_C
));
509 connect(main_ui_
->menuPacketComment
, &QMenu::aboutToShow
, this, &StratosharkMainWindow::setEditCommentsMenu
);
511 proto_tree_
= new ProtoTree(&master_split_
);
512 proto_tree_
->installEventFilter(this);
514 packet_list_
->setProtoTree(proto_tree_
);
515 packet_list_
->setProfileSwitcher(profile_switcher_
);
516 packet_list_
->installEventFilter(this);
518 main_stack_
= main_ui_
->mainStack
;
519 welcome_page_
= main_ui_
->welcomePage
;
520 main_status_bar_
= main_ui_
->statusBar
;
522 connect(proto_tree_
, &ProtoTree::fieldSelected
,
523 this, &StratosharkMainWindow::fieldSelected
);
524 connect(packet_list_
, &PacketList::fieldSelected
,
525 this, &StratosharkMainWindow::fieldSelected
);
526 connect(this, &StratosharkMainWindow::fieldSelected
,
527 this, &StratosharkMainWindow::setMenusForSelectedTreeRow
);
528 connect(this, &StratosharkMainWindow::fieldSelected
,
529 main_ui_
->statusBar
, &MainStatusBar::selectedFieldChanged
);
531 connect(this, &StratosharkMainWindow::fieldHighlight
,
532 main_ui_
->statusBar
, &MainStatusBar::highlightedFieldChanged
);
533 connect(mainApp
, &WiresharkApplication::captureActive
,
534 this, &StratosharkMainWindow::captureActive
);
536 byte_view_tab_
= new ByteViewTab(&master_split_
);
538 // Packet list and proto tree must exist before these are called.
539 setMenusForSelectedPacket();
540 setMenusForSelectedTreeRow();
542 initShowHideMainWidgets();
543 initTimeDisplayFormatMenu();
544 initTimePrecisionFormatMenu();
546 updatePreferenceActions();
547 updateRecentActions();
548 setForCaptureInProgress(false);
550 setTabOrder(df_combo_box_
->lineEdit(), packet_list_
);
551 setTabOrder(packet_list_
, proto_tree_
);
553 connect(&capture_file_
, &CaptureFile::captureEvent
, this, &StratosharkMainWindow::captureEventHandler
);
554 connect(&capture_file_
, &CaptureFile::captureEvent
, mainApp
, &WiresharkApplication::captureEventHandler
);
555 connect(&capture_file_
, &CaptureFile::captureEvent
, main_ui_
->statusBar
, &MainStatusBar::captureEventHandler
);
556 connect(&capture_file_
, &CaptureFile::captureEvent
, profile_switcher_
, &ProfileSwitcher::captureEventHandler
);
558 connect(mainApp
, &MainApplication::freezePacketList
, packet_list_
, &PacketList::freezePacketList
);
559 connect(mainApp
, &MainApplication::columnsChanged
, packet_list_
, &PacketList::columnsChanged
);
560 connect(mainApp
, &MainApplication::colorsChanged
, packet_list_
, &PacketList::colorsChanged
);
561 connect(mainApp
, &MainApplication::preferencesChanged
, packet_list_
, &PacketList::preferencesChanged
);
562 connect(mainApp
, &MainApplication::recentPreferencesRead
, this, &StratosharkMainWindow::applyRecentPaneGeometry
);
563 connect(mainApp
, &MainApplication::recentPreferencesRead
, this, &StratosharkMainWindow::updateRecentActions
);
564 connect(mainApp
, &MainApplication::packetDissectionChanged
, this, &StratosharkMainWindow::redissectPackets
, Qt::QueuedConnection
);
566 connect(mainApp
, &MainApplication::checkDisplayFilter
, this, &StratosharkMainWindow::checkDisplayFilter
);
567 connect(mainApp
, &MainApplication::fieldsChanged
, this, &StratosharkMainWindow::fieldsChanged
);
568 connect(mainApp
, &MainApplication::reloadLuaPlugins
, this, &StratosharkMainWindow::reloadLuaPlugins
);
570 connect(main_ui_
->mainStack
, &QStackedWidget::currentChanged
, this, &StratosharkMainWindow::mainStackChanged
);
572 connect(welcome_page_
, &WelcomePage::startCapture
, this, [this](QStringList
) { startCapture(); });
573 connect(welcome_page_
, &WelcomePage::recentFileActivated
, this, [this](QString cfile
) { openCaptureFile(cfile
); });
575 connect(main_ui_
->addressEditorFrame
, &AddressEditorFrame::redissectPackets
,
576 this, &StratosharkMainWindow::redissectPackets
);
577 connect(main_ui_
->addressEditorFrame
, &AddressEditorFrame::showNameResolutionPreferences
,
578 this, &StratosharkMainWindow::showPreferencesDialog
);
579 connect(main_ui_
->preferenceEditorFrame
, &PreferenceEditorFrame::showProtocolPreferences
,
580 this, &StratosharkMainWindow::showPreferencesDialog
);
581 connect(main_ui_
->filterExpressionFrame
, &FilterExpressionFrame::showPreferencesDialog
,
582 this, &StratosharkMainWindow::showPreferencesDialog
);
583 connect(main_ui_
->filterExpressionFrame
, &FilterExpressionFrame::filterExpressionsChanged
,
584 filter_expression_toolbar_
, &FilterExpressionToolBar::filterExpressionsChanged
);
586 /* Connect change of capture file */
587 connect(this, &StratosharkMainWindow::setCaptureFile
,
588 main_ui_
->searchFrame
, &SearchFrame::setCaptureFile
);
589 connect(this, &StratosharkMainWindow::setCaptureFile
,
590 main_ui_
->statusBar
, &MainStatusBar::setCaptureFile
);
591 connect(this, &StratosharkMainWindow::setCaptureFile
,
592 packet_list_
, &PacketList::setCaptureFile
);
593 connect(this, &StratosharkMainWindow::setCaptureFile
,
594 proto_tree_
, &ProtoTree::setCaptureFile
);
596 connect(mainApp
, &MainApplication::zoomMonospaceFont
, packet_list_
, &PacketList::setMonospaceFont
);
597 connect(mainApp
, &MainApplication::zoomMonospaceFont
, proto_tree_
, &ProtoTree::setMonospaceFont
);
599 connectFileMenuActions();
600 connectEditMenuActions();
601 connectViewMenuActions();
602 connectGoMenuActions();
603 connectCaptureMenuActions();
604 connectAnalyzeMenuActions();
605 connectStatisticsMenuActions();
606 connectToolsMenuActions();
607 connectHelpMenuActions();
609 connect(packet_list_
, &PacketList::packetDissectionChanged
, this, &StratosharkMainWindow::redissectPackets
);
610 connect(packet_list_
, &PacketList::showColumnPreferences
, this, &StratosharkMainWindow::showPreferencesDialog
);
611 connect(packet_list_
, &PacketList::showProtocolPreferences
, this, &StratosharkMainWindow::showPreferencesDialog
);
612 connect(packet_list_
, SIGNAL(editProtocolPreference(preference
*, pref_module
*)),
613 main_ui_
->preferenceEditorFrame
, SLOT(editPreference(preference
*, pref_module
*)));
614 connect(packet_list_
, &PacketList::editColumn
, this, &StratosharkMainWindow::showColumnEditor
);
615 connect(main_ui_
->columnEditorFrame
, &ColumnEditorFrame::columnEdited
, packet_list_
, &PacketList::columnsChanged
);
616 connect(packet_list_
, &QAbstractItemView::doubleClicked
, this, [=](const QModelIndex
&){ openPacketDialog(); });
617 connect(packet_list_
, &PacketList::packetListScrolled
, main_ui_
->actionGoAutoScroll
, &QAction::setChecked
);
619 connect(proto_tree_
, &ProtoTree::openPacketInNewWindow
, this, &StratosharkMainWindow::openPacketDialog
);
620 connect(proto_tree_
, &ProtoTree::showProtocolPreferences
, this, &StratosharkMainWindow::showPreferencesDialog
);
621 connect(proto_tree_
, SIGNAL(editProtocolPreference(preference
*, pref_module
*)),
622 main_ui_
->preferenceEditorFrame
, SLOT(editPreference(preference
*, pref_module
*)));
624 connect(main_ui_
->statusBar
, &MainStatusBar::showExpertInfo
, this, [=]() {
625 statCommandExpertInfo(NULL
, NULL
);
628 connect(main_ui_
->statusBar
, &MainStatusBar::stopLoading
,
629 &capture_file_
, &CaptureFile::stopLoading
);
631 connect(main_ui_
->statusBar
, &MainStatusBar::editCaptureComment
,
632 main_ui_
->actionStatisticsCaptureFileProperties
, &QAction::trigger
);
634 connect(main_ui_
->menuApplyAsFilter
, &QMenu::aboutToShow
,
635 this, &StratosharkMainWindow::filterMenuAboutToShow
);
636 connect(main_ui_
->menuPrepareAFilter
, &QMenu::aboutToShow
,
637 this, &StratosharkMainWindow::filterMenuAboutToShow
);
640 QTreeWidget
*iface_tree
= findChild
<QTreeWidget
*>("interfaceTree");
642 connect(iface_tree
, &QTreeWidget::itemSelectionChanged
, this, &StratosharkMainWindow::interfaceSelectionChanged
);
644 connect(main_ui_
->welcomePage
, &WelcomePage::captureFilterSyntaxChanged
,
645 this, &StratosharkMainWindow::captureFilterSyntaxChanged
);
647 connect(this, &StratosharkMainWindow::showExtcapOptions
, this, &StratosharkMainWindow::showExtcapOptionsDialog
);
648 connect(this->welcome_page_
, &WelcomePage::showExtcapOptions
, this, &StratosharkMainWindow::showExtcapOptionsDialog
);
650 #endif // HAVE_LIBPCAP
652 /* Create plugin_if hooks */
653 plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_APPLY
, plugin_if_mainwindow_apply_filter
);
654 plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_PREPARE
, plugin_if_mainwindow_apply_filter
);
655 plugin_if_register_gui_cb(PLUGIN_IF_PREFERENCE_SAVE
, plugin_if_mainwindow_preference
);
656 plugin_if_register_gui_cb(PLUGIN_IF_GOTO_FRAME
, plugin_if_mainwindow_gotoframe
);
658 plugin_if_register_gui_cb(PLUGIN_IF_GET_WS_INFO
, plugin_if_mainwindow_get_ws_info
);
660 plugin_if_register_gui_cb(PLUGIN_IF_GET_FRAME_DATA
, plugin_if_mainwindow_get_frame_data
);
661 plugin_if_register_gui_cb(PLUGIN_IF_GET_CAPTURE_FILE
, plugin_if_mainwindow_get_capture_file
);
662 plugin_if_register_gui_cb(PLUGIN_IF_REMOVE_TOOLBAR
, plugin_if_mainwindow_update_toolbars
);
664 /* Register Interface Toolbar callbacks */
665 iface_toolbar_register_cb(mainwindow_add_toolbar
, mainwindow_remove_toolbar
);
667 /* Show tooltips on menu items that go to websites */
668 main_ui_
->actionHelpMPWireshark
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_WIRESHARK
)));
669 main_ui_
->actionHelpMPWireshark_Filter
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_WIRESHARK_FILTER
)));
670 main_ui_
->actionHelpMPCapinfos
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_CAPINFOS
)));
671 main_ui_
->actionHelpMPDumpcap
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_DUMPCAP
)));
672 main_ui_
->actionHelpMPEditcap
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_EDITCAP
)));
673 main_ui_
->actionHelpMPMergecap
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_MERGECAP
)));
674 main_ui_
->actionHelpMPRawshark
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_RAWSHARK
)));
675 main_ui_
->actionHelpMPReordercap
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_REORDERCAP
)));
676 main_ui_
->actionHelpMPText2pcap
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_TEXT2PCAP
)));
677 main_ui_
->actionHelpMPTShark
->setToolTip(gchar_free_to_qstring(topic_action_url(LOCALPAGE_MAN_TSHARK
)));
679 main_ui_
->actionHelpContents
->setToolTip(gchar_free_to_qstring(topic_action_url(HELP_CONTENT
)));
680 main_ui_
->actionHelpWebsite
->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_HOME
)));
681 main_ui_
->actionHelpFAQ
->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_FAQ
)));
682 main_ui_
->actionHelpAsk
->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_ASK
)));
683 main_ui_
->actionHelpDownloads
->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_DOWNLOAD
)));
684 main_ui_
->actionHelpWiki
->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_WIKI
)));
685 main_ui_
->actionHelpSampleCaptures
->setToolTip(gchar_free_to_qstring(topic_action_url(ONLINEPAGE_SAMPLE_CAPTURES
)));
690 StratosharkMainWindow::~StratosharkMainWindow()
692 disconnect(main_ui_
->mainStack
, 0, 0, 0);
693 if (previous_focus_
!= nullptr) {
694 disconnect(previous_focus_
, &QWidget::destroyed
, this, &StratosharkMainWindow::resetPreviousFocus
);
698 // Below dialogs inherit GeometryStateDialog
699 // For reasons described in geometry_state_dialog.h no parent is set when
700 // instantiating the dialogs and as a result objects are not automatically
701 // freed by its parent. Free then here explicitly to avoid leak and numerous
702 // Valgrind complaints.
703 delete file_set_dialog_
;
705 delete capture_options_dialog_
;
712 QMenu
*StratosharkMainWindow::createPopupMenu()
714 QMenu
*menu
= new QMenu();
715 menu
->addAction(main_ui_
->actionViewMainToolbar
);
716 menu
->addAction(main_ui_
->actionViewFilterToolbar
);
718 if (!main_ui_
->menuInterfaceToolbars
->actions().isEmpty()) {
719 QMenu
*submenu
= menu
->addMenu(main_ui_
->menuInterfaceToolbars
->title());
720 foreach(QAction
*action
, main_ui_
->menuInterfaceToolbars
->actions()) {
721 submenu
->addAction(action
);
725 if (!main_ui_
->menuAdditionalToolbars
->actions().isEmpty()) {
726 QMenu
*subMenu
= menu
->addMenu(main_ui_
->menuAdditionalToolbars
->title());
727 foreach(QAction
*action
, main_ui_
->menuAdditionalToolbars
->actions()) {
728 subMenu
->addAction(action
);
732 menu
->addAction(main_ui_
->actionViewStatusBar
);
734 menu
->addSeparator();
735 menu
->addAction(main_ui_
->actionViewPacketList
);
736 menu
->addAction(main_ui_
->actionViewPacketDetails
);
737 menu
->addAction(main_ui_
->actionViewPacketBytes
);
741 void StratosharkMainWindow::addInterfaceToolbar(const iface_toolbar
*toolbar_entry
)
743 QMenu
*menu
= main_ui_
->menuInterfaceToolbars
;
744 bool visible
= g_list_find_custom(recent
.interface_toolbars
, toolbar_entry
->menu_title
, (GCompareFunc
)strcmp
) ? true : false;
746 QString title
= QString().fromUtf8(toolbar_entry
->menu_title
);
747 QAction
*action
= new QAction(title
, menu
);
748 action
->setEnabled(true);
749 action
->setCheckable(true);
750 action
->setChecked(visible
);
751 action
->setToolTip(tr("Show or hide the toolbar"));
753 QAction
*before
= NULL
;
754 foreach(QAction
*action
, menu
->actions()) {
755 // Ensure we add the menu entries in sorted order
756 if (action
->text().compare(title
, Qt::CaseInsensitive
) > 0) {
761 menu
->insertAction(before
, action
);
763 InterfaceToolbar
*interface_toolbar
= new InterfaceToolbar(this, toolbar_entry
);
764 connect(mainApp
, &MainApplication::appInitialized
, interface_toolbar
, &InterfaceToolbar::interfaceListChanged
);
765 connect(mainApp
, &MainApplication::localInterfaceListChanged
, interface_toolbar
, &InterfaceToolbar::interfaceListChanged
);
767 QToolBar
*toolbar
= new QToolBar(this);
768 toolbar
->addWidget(interface_toolbar
);
769 toolbar
->setMovable(false);
770 toolbar
->setVisible(visible
);
772 action
->setData(QVariant::fromValue(toolbar
));
774 addToolBar(Qt::TopToolBarArea
, toolbar
);
775 insertToolBarBreak(toolbar
);
777 if (show_hide_actions_
) {
778 show_hide_actions_
->addAction(action
);
781 menu
->menuAction()->setVisible(true);
784 void StratosharkMainWindow::removeInterfaceToolbar(const char *menu_title
)
786 QMenu
*menu
= main_ui_
->menuInterfaceToolbars
;
787 QAction
*action
= NULL
;
788 QMap
<QAction
*, QWidget
*>::iterator i
;
790 QString title
= QString().fromUtf8(menu_title
);
791 foreach(action
, menu
->actions()) {
792 if (title
.compare(action
->text()) == 0) {
798 if (show_hide_actions_
) {
799 show_hide_actions_
->removeAction(action
);
801 menu
->removeAction(action
);
803 QToolBar
*toolbar
= action
->data().value
<QToolBar
*>();
804 removeToolBar(toolbar
);
810 menu
->menuAction()->setVisible(!menu
->actions().isEmpty());
813 void StratosharkMainWindow::updateStyleSheet()
816 // TODO: The event type QEvent::ApplicationPaletteChange is not sent to all child widgets.
817 // Workaround this by doing it manually for all AccordionFrame.
818 main_ui_
->addressEditorFrame
->updateStyleSheet();
819 main_ui_
->columnEditorFrame
->updateStyleSheet();
820 main_ui_
->filterExpressionFrame
->updateStyleSheet();
821 main_ui_
->goToFrame
->updateStyleSheet();
822 main_ui_
->preferenceEditorFrame
->updateStyleSheet();
823 main_ui_
->searchFrame
->updateStyleSheet();
825 df_combo_box_
->updateStyleSheet();
826 welcome_page_
->updateStyleSheets();
830 bool StratosharkMainWindow::eventFilter(QObject
*obj
, QEvent
*event
) {
832 // The user typed some text. Start filling in a filter.
833 // We may need to be more choosy here. We just need to catch events for the packet list,
834 // proto tree, and main welcome widgets.
835 if (event
->type() == QEvent::KeyPress
) {
836 QKeyEvent
*kevt
= static_cast<QKeyEvent
*>(event
);
837 if (kevt
->text().length() > 0 && kevt
->text()[0].isPrint() &&
838 !(kevt
->modifiers() & Qt::ControlModifier
)) {
839 df_combo_box_
->lineEdit()->insert(kevt
->text());
840 df_combo_box_
->lineEdit()->setFocus();
845 return QMainWindow::eventFilter(obj
, event
);
848 bool StratosharkMainWindow::event(QEvent
*event
)
850 switch (event
->type()) {
851 case QEvent::ApplicationPaletteChange
:
852 initMainToolbarIcons();
859 return QMainWindow::event(event
);
862 void StratosharkMainWindow::keyPressEvent(QKeyEvent
*event
) {
864 // Explicitly focus on the display filter combo.
865 if (event
->modifiers() & Qt::ControlModifier
&& event
->key() == Qt::Key_Slash
) {
866 df_combo_box_
->setFocus(Qt::ShortcutFocusReason
);
870 if (mainApp
->focusWidget() == main_ui_
->goToLineEdit
) {
871 if (event
->modifiers() == Qt::NoModifier
|| event
->modifiers() == Qt::KeypadModifier
) {
872 if (event
->key() == Qt::Key_Escape
) {
874 } else if (event
->key() == Qt::Key_Enter
|| event
->key() == Qt::Key_Return
) {
878 return; // goToLineEdit didn't want it and we don't either.
881 // Move up & down the packet list.
882 if (event
->key() == Qt::Key_F7
) {
883 packet_list_
->goPreviousPacket();
884 } else if (event
->key() == Qt::Key_F8
) {
885 packet_list_
->goNextPacket();
888 // Move along, citizen.
889 QMainWindow::keyPressEvent(event
);
892 void StratosharkMainWindow::closeEvent(QCloseEvent
*event
) {
893 saveWindowGeometry();
895 /* If we're in the middle of stopping a capture, don't do anything;
896 the user can try deleting the window after the capture stops. */
897 if (capture_stopping_
) {
902 QString
before_what(tr(" before quitting"));
903 if (!testCaptureFileClose(before_what
, Quit
)) {
909 if (capture_options_dialog_
) capture_options_dialog_
->close();
911 // Make sure we kill any open dumpcap processes.
912 delete welcome_page_
;
914 // One of the many places we assume one main window.
915 if (!mainApp
->isInitialized()) {
916 // If we're still initializing, QCoreApplication::quit() won't
917 // exit properly because we are not in the event loop. This
918 // means that the application won't clean up after itself. We
919 // might want to call mainApp->processEvents() during startup
920 // instead so that we can do a normal exit here.
924 // When the main loop is not yet running (i.e. when openCaptureFile is
925 // executing in main.cpp), the above quit action has no effect.
926 // Schedule a quit action for the next execution of the main loop.
927 QMetaObject::invokeMethod(mainApp
, "quit", Qt::QueuedConnection
);
930 // XXX On windows the drag description is "Copy". It should be "Open" or
931 // "Merge" as appropriate. It looks like we need access to IDataObject in
932 // order to set DROPDESCRIPTION.
933 void StratosharkMainWindow::dragEnterEvent(QDragEnterEvent
*event
)
935 if (!event
->mimeData()->hasUrls())
941 if (!main_ui_
->actionFileOpen
->isEnabled()) {
942 // We could alternatively call setAcceptDrops(!capture_in_progress)
943 // in setMenusForCaptureInProgress but that wouldn't provide feedback.
945 mainApp
->pushStatus(WiresharkApplication::TemporaryStatus
, tr("Unable to drop files during capture."));
946 event
->setDropAction(Qt::IgnoreAction
);
951 bool have_files
= false;
952 foreach(QUrl drag_url
, event
->mimeData()->urls()) {
953 if (!drag_url
.toLocalFile().isEmpty()) {
960 event
->acceptProposedAction();
964 void StratosharkMainWindow::dropEvent(QDropEvent
*event
)
966 if (!event
->mimeData()->hasUrls())
972 QList
<QByteArray
> local_files
;
973 int max_dropped_files
= 100; // Arbitrary
975 foreach(QUrl drop_url
, event
->mimeData()->urls()) {
976 QString drop_file
= drop_url
.toLocalFile();
977 if (!drop_file
.isEmpty()) {
978 local_files
<< drop_file
.toUtf8();
979 if (local_files
.size() >= max_dropped_files
) {
985 event
->acceptProposedAction();
987 if (local_files
.size() < 1) {
994 if (local_files
.size() == 1) {
995 openCaptureFile(local_files
.at(0));
999 const char **in_filenames
= g_new(const char *, local_files
.size());
1000 char *tmpname
= NULL
;
1002 for (int i
= 0; i
< local_files
.size(); i
++) {
1003 in_filenames
[i
] = local_files
.at(i
).constData();
1006 /* merge the files in chronological order */
1007 if (cf_merge_files_to_tempfile(this, global_capture_opts
.temp_dir
, &tmpname
, static_cast<int>(local_files
.size()),
1009 wtap_pcapng_file_type_subtype(),
1011 /* Merge succeeded; close the currently-open file and try
1012 to open the merged capture file. */
1013 openCaptureFile(tmpname
, QString(), WTAP_TYPE_AUTO
, true);
1017 g_free(in_filenames
);
1020 // Apply recent settings to the main window geometry.
1021 // We haven't loaded the preferences at this point so we assume that the
1022 // position and size preference are enabled.
1023 // Note we might end up with unexpected screen geometries if the user
1024 // unplugs or plugs in a monitor:
1025 // https://bugreports.qt.io/browse/QTBUG-44213
1026 void StratosharkMainWindow::loadWindowGeometry()
1028 int min_sensible_dimension
= 200;
1031 if (recent
.gui_geometry_main_maximized
) {
1032 // [save|restore]Geometry does a better job (on Linux and Windows)
1033 // of restoring to the original monitor because it saves
1034 // QGuiApplication::screens().indexOf(screen())
1035 // (it also saves Qt::WindowFullScreen, restores the non-maximized
1036 // size even when starting out maximized, etc.)
1037 // Monitors of different DPI might still be tricky:
1038 // https://bugreports.qt.io/browse/QTBUG-70721
1039 // https://bugreports.qt.io/browse/QTBUG-77385
1041 // We might eventually want to always use restoreGeometry, but
1042 // for now at least use it just for maximized because it's better
1043 // then what we've been doing.
1044 setWindowState(Qt::WindowMaximized
);
1048 QRect
recent_geom(recent
.gui_geometry_main_x
, recent
.gui_geometry_main_y
,
1049 recent
.gui_geometry_main_width
, recent
.gui_geometry_main_height
);
1050 if (!rect_on_screen(recent_geom
)) {
1051 // We're not visible on any screens. See if we can move onscreen
1052 // without resizing.
1053 recent_geom
.moveTo(50, 50); // recent.c defaults to 20.
1056 if (!rect_on_screen(recent_geom
)) {
1057 // Give up and use the default geometry.
1061 // if (prefs.gui_geometry_save_position) {
1062 move(recent_geom
.topLeft());
1065 if (// prefs.gui_geometry_save_size &&
1066 recent_geom
.width() > min_sensible_dimension
&&
1067 recent_geom
.height() > min_sensible_dimension
) {
1068 resize(recent_geom
.size());
1073 void StratosharkMainWindow::saveWindowGeometry()
1075 if (prefs
.gui_geometry_save_position
||
1076 prefs
.gui_geometry_save_size
||
1077 prefs
.gui_geometry_save_maximized
) {
1078 g_free(recent
.gui_geometry_main
);
1079 recent
.gui_geometry_main
= g_strdup(saveGeometry().toHex().constData());
1082 if (prefs
.gui_geometry_save_position
) {
1083 recent
.gui_geometry_main_x
= pos().x();
1084 recent
.gui_geometry_main_y
= pos().y();
1087 if (prefs
.gui_geometry_save_size
) {
1088 recent
.gui_geometry_main_width
= size().width();
1089 recent
.gui_geometry_main_height
= size().height();
1092 if (prefs
.gui_geometry_save_maximized
) {
1093 // On macOS this is false when it shouldn't be
1094 // XXX: Does save/restoreGeometry work any better on macOS
1095 // for maximized windows? Apparently not:
1096 // https://bugreports.qt.io/browse/QTBUG-100272
1097 recent
.gui_geometry_main_maximized
= isMaximized();
1100 g_free(recent
.gui_geometry_main_master_split
);
1101 g_free(recent
.gui_geometry_main_extra_split
);
1102 recent
.gui_geometry_main_master_split
= g_strdup(master_split_
.saveState().toHex().constData());
1103 recent
.gui_geometry_main_extra_split
= g_strdup(extra_split_
.saveState().toHex().constData());
1105 // Saving the QSplitter state is more accurate (#19361), but save
1106 // the old GTK-style pane information for backwards compatibility
1107 // for switching back and forth with older versions.
1108 if (master_split_
.sizes().length() > 0) {
1109 recent
.gui_geometry_main_upper_pane
= master_split_
.sizes()[0];
1112 if (master_split_
.sizes().length() > 2) {
1113 recent
.gui_geometry_main_lower_pane
= master_split_
.sizes()[1];
1114 } else if (extra_split_
.sizes().length() > 0) {
1115 recent
.gui_geometry_main_lower_pane
= extra_split_
.sizes()[0];
1119 // Our event loop becomes nested whenever we call update_progress_dlg, which
1120 // includes several places in file.c. The GTK+ UI stays out of trouble by
1121 // showing a modal progress dialog. We attempt to do the equivalent below by
1122 // disabling parts of the main window. At a minimum the ProgressFrame in the
1123 // main status bar must remain accessible.
1125 // We might want to do this any time the main status bar progress frame is
1126 // shown and hidden.
1127 void StratosharkMainWindow::freeze()
1129 freeze_focus_
= mainApp
->focusWidget();
1131 // XXX Alternatively we could just disable and enable the main menu.
1132 for (int i
= 0; i
< freeze_actions_
.size(); i
++) {
1133 QAction
*action
= freeze_actions_
[i
].first
;
1134 freeze_actions_
[i
].second
= action
->isEnabled();
1135 action
->setEnabled(false);
1137 main_ui_
->centralWidget
->setEnabled(false);
1140 void StratosharkMainWindow::thaw()
1142 main_ui_
->centralWidget
->setEnabled(true);
1143 for (int i
= 0; i
< freeze_actions_
.size(); i
++) {
1144 freeze_actions_
[i
].first
->setEnabled(freeze_actions_
[i
].second
);
1147 if (freeze_focus_
) freeze_focus_
->setFocus();
1148 freeze_focus_
= NULL
;
1151 void StratosharkMainWindow::mergeCaptureFile()
1153 QString file_name
= "";
1154 QString read_filter
= "";
1155 dfilter_t
*rfcode
= NULL
;
1158 if (!capture_file_
.capFile())
1161 if (prefs
.gui_ask_unsaved
) {
1162 if (cf_has_unsaved_data(capture_file_
.capFile())) {
1163 QMessageBox msg_dialog
;
1164 char *display_basename
;
1167 msg_dialog
.setIcon(QMessageBox::Question
);
1168 /* This file has unsaved data; ask the user whether to save
1170 if (capture_file_
.capFile()->is_tempfile
) {
1171 msg_dialog
.setText(tr("Save packets before merging?"));
1172 msg_dialog
.setInformativeText(tr("A temporary capture file can't be merged."));
1175 * Format the message.
1177 display_basename
= g_filename_display_basename(capture_file_
.capFile()->filename
);
1178 msg_dialog
.setText(tr("Save changes in \"%1\" before merging?").arg(display_basename
));
1179 g_free(display_basename
);
1180 msg_dialog
.setInformativeText(tr("Changes must be saved before the files can be merged."));
1183 msg_dialog
.setStandardButtons(QMessageBox::Save
| QMessageBox::Cancel
);
1184 msg_dialog
.setDefaultButton(QMessageBox::Save
);
1186 response
= msg_dialog
.exec();
1190 case QMessageBox::Save
:
1191 /* Save the file but don't close it */
1192 saveCaptureFile(capture_file_
.capFile(), false);
1195 case QMessageBox::Cancel
:
1197 /* Don't do the merge. */
1204 CaptureFileDialog
merge_dlg(this, capture_file_
.capFile());
1206 cf_status_t merge_status
;
1207 char *in_filenames
[2];
1210 if (merge_dlg
.merge(file_name
, read_filter
)) {
1211 df_error_t
*df_err
= NULL
;
1213 if (!dfilter_compile(qUtf8Printable(read_filter
), &rfcode
, &df_err
)) {
1214 /* Not valid. Tell the user, and go back and run the file
1215 selection box again once they dismiss the alert. */
1216 // Similar to commandline_info.jfilter section in main().
1217 QMessageBox::warning(this, tr("Invalid Read Filter"),
1218 tr("The filter expression %1 isn't a valid read filter. (%2).").arg(read_filter
, df_err
->msg
),
1220 df_error_free(&df_err
);
1227 file_type
= capture_file_
.capFile()->cd_t
;
1229 /* Try to merge or append the two files */
1230 if (merge_dlg
.mergeType() == 0) {
1231 /* chronological order */
1232 in_filenames
[0] = g_strdup(capture_file_
.capFile()->filename
);
1233 in_filenames
[1] = qstring_strdup(file_name
);
1234 merge_status
= cf_merge_files_to_tempfile(this, global_capture_opts
.temp_dir
, &tmpname
, 2, in_filenames
, file_type
, false);
1235 } else if (merge_dlg
.mergeType() <= 0) {
1237 in_filenames
[0] = qstring_strdup(file_name
);
1238 in_filenames
[1] = g_strdup(capture_file_
.capFile()->filename
);
1239 merge_status
= cf_merge_files_to_tempfile(this, global_capture_opts
.temp_dir
, &tmpname
, 2, in_filenames
, file_type
, true);
1242 in_filenames
[0] = g_strdup(capture_file_
.capFile()->filename
);
1243 in_filenames
[1] = qstring_strdup(file_name
);
1244 merge_status
= cf_merge_files_to_tempfile(this, global_capture_opts
.temp_dir
, &tmpname
, 2, in_filenames
, file_type
, true);
1247 g_free(in_filenames
[0]);
1248 g_free(in_filenames
[1]);
1250 if (merge_status
!= CF_OK
) {
1251 dfilter_free(rfcode
);
1256 cf_close(capture_file_
.capFile());
1258 /* Try to open the merged capture file. */
1259 // XXX - Just free rfcode and call
1260 // openCaptureFile(tmpname, read_filter, WTAP_TYPE_AUTO, true);
1261 CaptureFile::globalCapFile()->window
= this;
1262 if (cf_open(CaptureFile::globalCapFile(), tmpname
, WTAP_TYPE_AUTO
, true /* temporary file */, &err
) != CF_OK
) {
1263 /* We couldn't open it; fail. */
1264 CaptureFile::globalCapFile()->window
= NULL
;
1265 dfilter_free(rfcode
);
1270 /* Attach the new read filter to "cf" ("cf_open()" succeeded, so
1271 it closed the previous capture file, and thus destroyed any
1272 previous read filter attached to "cf"). */
1273 cf_set_rfcode(CaptureFile::globalCapFile(), rfcode
);
1275 switch (cf_read(CaptureFile::globalCapFile(), /*reloading=*/false)) {
1279 /* Just because we got an error, that doesn't mean we were unable
1280 to read any of the file; we handle what we could get from the
1284 case CF_READ_ABORTED
:
1285 /* The user bailed out of re-reading the capture file; the
1286 capture file has been closed - just free the capture file name
1287 string and return (without changing the last containing
1293 /* This is a tempfile; don't change the last open directory. */
1295 main_ui_
->statusBar
->showExpert();
1301 void StratosharkMainWindow::importCaptureFile() {
1302 ImportTextDialog import_dlg
;
1304 QString
before_what(tr(" before importing a capture"));
1305 if (!testCaptureFileClose(before_what
))
1310 if (import_dlg
.result() != QDialog::Accepted
) {
1315 openCaptureFile(import_dlg
.capfileName(), QString(), WTAP_TYPE_AUTO
, true);
1318 bool StratosharkMainWindow::saveCaptureFile(capture_file
*cf
, bool dont_reopen
) {
1320 bool discard_comments
;
1322 if (cf
->is_tempfile
) {
1323 /* This is a temporary capture file, so saving it means saving
1324 it to a permanent file. Prompt the user for a location
1325 to which to save it. Don't require that the file format
1326 support comments - if it's a temporary capture file, it's
1327 probably pcapng, which supports comments and, if it's
1328 not pcapng, let the user decide what they want to do
1329 if they've added comments. */
1330 return saveAsCaptureFile(cf
, false, dont_reopen
);
1332 if (cf
->unsaved_changes
) {
1333 cf_write_status_t status
;
1335 /* This is not a temporary capture file, but it has unsaved
1336 changes, so saving it means doing a "safe save" on top
1337 of the existing file, in the same format - no UI needed
1338 unless the file has comments and the file's format doesn't
1341 If the file has comments, does the file's format support them?
1342 If not, ask the user whether they want to discard the comments
1343 or choose a different format. */
1344 switch (CaptureFileDialog::checkSaveAsWithComments(this, cf
, cf
->cd_t
)) {
1347 /* The file can be saved in the specified format as is;
1348 just drive on and save in the format they selected. */
1349 discard_comments
= false;
1352 case SAVE_WITHOUT_COMMENTS
:
1353 /* The file can't be saved in the specified format as is,
1354 but it can be saved without the comments, and the user
1355 said "OK, discard the comments", so save it in the
1356 format they specified without the comments. */
1357 discard_comments
= true;
1360 case SAVE_IN_ANOTHER_FORMAT
:
1361 /* There are file formats in which we can save this that
1362 support comments, and the user said not to delete the
1363 comments. Do a "Save As" so the user can select
1364 one of those formats and choose a file name. */
1365 return saveAsCaptureFile(cf
, true, dont_reopen
);
1368 /* The user said "forget it". Just return. */
1372 /* Squelch warnings that discard_comments is being used
1374 ws_assert_not_reached();
1378 /* XXX - cf->filename might get freed out from under us, because
1379 the code path through which cf_save_records() goes currently
1380 closes the current file and then opens and reloads the saved file,
1381 so make a copy and free it later. */
1382 file_name
= cf
->filename
;
1383 status
= cf_save_records(cf
, qUtf8Printable(file_name
), cf
->cd_t
, cf
->compression_type
,
1384 discard_comments
, dont_reopen
);
1388 /* The save succeeded; we're done.
1389 If we discarded comments, redraw the packet list to reflect
1390 any packets that no longer have comments. If we had unsaved
1391 changes, redraw the packet list, because saving a time
1392 shift zeroes out the frame.offset_shift field.
1393 If we had a color filter based on frame data, recolor. */
1394 /* XXX: If there is a filter based on those, we want to force
1395 a rescan with the current filter (we don't actually
1398 if (discard_comments
|| cf
->unsaved_changes
) {
1399 if (color_filters_use_proto(proto_get_id_by_filter_name("frame"))) {
1400 packet_list_
->recolorPackets();
1402 packet_list_
->redrawVisiblePackets();
1406 cf
->unsaved_changes
= false; //we just saved so we signal that we have no unsaved changes
1407 updateForUnsavedChanges(); // we update the title bar to remove the *
1410 case CF_WRITE_ERROR
:
1411 /* The write failed.
1412 XXX - OK, what do we do now? Let them try a
1413 "Save As", in case they want to try to save to a
1414 different directory or file system? */
1417 case CF_WRITE_ABORTED
:
1418 /* The write was aborted; just drive on. */
1422 /* Otherwise just do nothing. */
1428 bool StratosharkMainWindow::saveAsCaptureFile(capture_file
*cf
, bool must_support_comments
, bool dont_reopen
) {
1429 QString file_name
= "";
1431 wtap_compression_type compression_type
;
1432 cf_write_status_t status
;
1434 bool discard_comments
= false;
1441 CaptureFileDialog
save_as_dlg(this, cf
);
1443 /* If the file has comments, does the format the user selected
1444 support them? If not, ask the user whether they want to
1445 discard the comments or choose a different format. */
1446 switch (save_as_dlg
.saveAs(file_name
, must_support_comments
)) {
1449 /* The file can be saved in the specified format as is;
1450 just drive on and save in the format they selected. */
1451 discard_comments
= false;
1454 case SAVE_WITHOUT_COMMENTS
:
1455 /* The file can't be saved in the specified format as is,
1456 but it can be saved without the comments, and the user
1457 said "OK, discard the comments", so save it in the
1458 format they specified without the comments. */
1459 discard_comments
= true;
1462 case SAVE_IN_ANOTHER_FORMAT
:
1463 /* There are file formats in which we can save this that
1464 support comments, and the user said not to delete the
1465 comments. The combo box of file formats has had the
1466 formats that don't support comments trimmed from it,
1467 so run the dialog again, to let the user decide
1468 whether to save in one of those formats or give up. */
1469 must_support_comments
= true;
1473 /* The user said "forget it". Just get rid of the dialog box
1477 file_type
= save_as_dlg
.selectedFileType();
1478 if (file_type
== WTAP_FILE_TYPE_SUBTYPE_UNKNOWN
) {
1479 /* This "should not happen". */
1480 QMessageBox msg_dialog
;
1482 msg_dialog
.setIcon(QMessageBox::Critical
);
1483 msg_dialog
.setText(tr("Unknown file type returned by merge dialog."));
1484 msg_dialog
.setInformativeText(tr("Please report this as a Stratoshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
1488 compression_type
= save_as_dlg
.compressionType();
1491 // /* If the file exists and it's user-immutable or not writable,
1492 // ask the user whether they want to override that. */
1493 // if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) {
1494 // /* They don't. Let them try another file name or cancel. */
1499 /* Attempt to save the file */
1500 status
= cf_save_records(cf
, qUtf8Printable(file_name
), file_type
, compression_type
,
1501 discard_comments
, dont_reopen
);
1505 /* The save succeeded; we're done. */
1506 /* Save the directory name for future file dialogs. */
1507 dirname
= qstring_strdup(file_name
); /* Overwrites cf_name */
1508 set_last_open_dir(get_dirname(dirname
));
1510 /* If we discarded comments, redraw the packet list to reflect
1511 any packets that no longer have comments. If we had unsaved
1512 changes, redraw the packet list, because saving a time
1513 shift zeroes out the frame.offset_shift field.
1514 If we had a color filter based on frame data, recolor. */
1515 /* XXX: If there is a filter based on those, we want to force
1516 a rescan with the current filter (we don't actually
1519 if (discard_comments
|| cf
->unsaved_changes
) {
1520 if (color_filters_use_proto(proto_get_id_by_filter_name("frame"))) {
1521 packet_list_
->recolorPackets();
1523 packet_list_
->redrawVisiblePackets();
1527 cf
->unsaved_changes
= false; //we just saved so we signal that we have no unsaved changes
1528 updateForUnsavedChanges(); // we update the title bar to remove the *
1529 /* Add this filename to the list of recent files in the "Recent Files" submenu */
1530 add_menu_recent_capture_file(qUtf8Printable(file_name
), false);
1533 case CF_WRITE_ERROR
:
1534 /* The save failed; let the user try again. */
1537 case CF_WRITE_ABORTED
:
1538 /* The user aborted the save; just return. */
1545 void StratosharkMainWindow::exportSelectedPackets() {
1546 QString file_name
= "";
1548 wtap_compression_type compression_type
;
1549 packet_range_t range
;
1550 cf_write_status_t status
;
1552 bool discard_comments
= false;
1554 if (!capture_file_
.capFile())
1557 /* Init the packet range */
1558 packet_range_init(&range
, capture_file_
.capFile());
1559 range
.process_filtered
= true;
1560 range
.include_dependents
= true;
1562 QList
<int> rows
= packet_list_
->selectedRows(true);
1564 QStringList entries
;
1565 foreach (int row
, rows
)
1566 entries
<< QString::number(row
);
1567 QString selRange
= entries
.join(",");
1570 CaptureFileDialog
esp_dlg(this, capture_file_
.capFile());
1572 /* If the file has comments, does the format the user selected
1573 support them? If not, ask the user whether they want to
1574 discard the comments or choose a different format. */
1575 switch (esp_dlg
.exportSelectedPackets(file_name
, &range
, selRange
)) {
1578 /* The file can be saved in the specified format as is;
1579 just drive on and save in the format they selected. */
1580 discard_comments
= false;
1583 case SAVE_WITHOUT_COMMENTS
:
1584 /* The file can't be saved in the specified format as is,
1585 but it can be saved without the comments, and the user
1586 said "OK, discard the comments", so save it in the
1587 format they specified without the comments. */
1588 discard_comments
= true;
1591 case SAVE_IN_ANOTHER_FORMAT
:
1592 /* There are file formats in which we can save this that
1593 support comments, and the user said not to delete the
1594 comments. The combo box of file formats has had the
1595 formats that don't support comments trimmed from it,
1596 so run the dialog again, to let the user decide
1597 whether to save in one of those formats or give up. */
1601 /* The user said "forget it". Just get rid of the dialog box
1607 * Check that we're not going to save on top of the current
1609 * We do it here so we catch all cases ...
1610 * Unfortunately, the file requester gives us an absolute file
1611 * name and the read file name may be relative (if supplied on
1612 * the command line). From Joerg Mayer.
1614 if (files_identical(capture_file_
.capFile()->filename
, qUtf8Printable(file_name
))) {
1615 QMessageBox msg_box
;
1616 char *display_basename
= g_filename_display_basename(qUtf8Printable(file_name
));
1618 msg_box
.setIcon(QMessageBox::Critical
);
1619 msg_box
.setText(tr("Unable to export to \"%1\".").arg(display_basename
));
1620 msg_box
.setInformativeText(tr("You cannot export packets to the current capture file."));
1621 msg_box
.setStandardButtons(QMessageBox::Ok
);
1622 msg_box
.setDefaultButton(QMessageBox::Ok
);
1624 g_free(display_basename
);
1628 file_type
= esp_dlg
.selectedFileType();
1629 if (file_type
== WTAP_FILE_TYPE_SUBTYPE_UNKNOWN
) {
1630 /* This "should not happen". */
1631 QMessageBox msg_box
;
1633 msg_box
.setIcon(QMessageBox::Critical
);
1634 msg_box
.setText(tr("Unknown file type returned by export dialog."));
1635 msg_box
.setInformativeText(tr("Please report this as a Stratoshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
1639 compression_type
= esp_dlg
.compressionType();
1642 // /* If the file exists and it's user-immutable or not writable,
1643 // ask the user whether they want to override that. */
1644 // if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) {
1645 // /* They don't. Let them try another file name or cancel. */
1650 /* Attempt to save the file */
1651 status
= cf_export_specified_packets(capture_file_
.capFile(), qUtf8Printable(file_name
), &range
, file_type
, compression_type
);
1655 /* The save succeeded; we're done. */
1656 /* Save the directory name for future file dialogs. */
1657 dirname
= qstring_strdup(file_name
); /* Overwrites cf_name */
1658 set_last_open_dir(get_dirname(dirname
));
1660 /* If we discarded comments, redraw the packet list to reflect
1661 any packets that no longer have comments. */
1662 /* XXX: Why? We're exporting some packets to a new file but not
1663 changing our current capture file, that shouldn't change the
1664 current packet list. */
1665 if (discard_comments
)
1666 packet_list_
->redrawVisiblePackets();
1667 /* Add this filename to the list of recent files in the "Recent Files" submenu */
1668 add_menu_recent_capture_file(qUtf8Printable(file_name
), false);
1671 case CF_WRITE_ERROR
:
1672 /* The save failed; let the user try again. */
1675 case CF_WRITE_ABORTED
:
1676 /* The user aborted the save; just return. */
1682 packet_range_cleanup(&range
);
1685 void StratosharkMainWindow::exportDissections(export_type_e export_type
) {
1686 capture_file
*cf
= capture_file_
.capFile();
1687 g_return_if_fail(cf
);
1689 QList
<int> rows
= packet_list_
->selectedRows(true);
1691 QStringList entries
;
1692 foreach (int row
, rows
)
1693 entries
<< QString::number(row
);
1694 QString selRange
= entries
.join(",");
1696 ExportDissectionDialog
*ed_dlg
= new ExportDissectionDialog(this, cf
, export_type
, selRange
);
1697 ed_dlg
->setWindowModality(Qt::ApplicationModal
);
1698 ed_dlg
->setAttribute(Qt::WA_DeleteOnClose
);
1702 bool StratosharkMainWindow::testCaptureFileClose(QString before_what
, FileCloseContext context
) {
1703 bool capture_in_progress
= false;
1704 bool do_close_file
= false;
1706 if (!capture_file_
.capFile() || capture_file_
.capFile()->state
== FILE_CLOSED
)
1707 return true; /* Already closed, nothing to do */
1709 if (capture_file_
.capFile()->read_lock
) {
1711 * If the file is being redissected, we cannot stop the capture since
1712 * that would crash and burn "cf_read", so stop early. Ideally all
1713 * callers should be modified to check this condition and act
1714 * accordingly (ignore action or queue it up), so print a warning.
1716 ws_warning("Refusing to close \"%s\" which is being read.", capture_file_
.capFile()->filename
);
1721 if (capture_file_
.capFile()->state
== FILE_READ_IN_PROGRESS
||
1722 capture_file_
.capFile()->state
== FILE_READ_PENDING
) {
1724 * FILE_READ_IN_PROGRESS is true if we're reading a capture file
1725 * *or* if we're doing a live capture. From the capture file itself we
1726 * cannot differentiate the cases, so check the current capture session.
1727 * FILE_READ_PENDING is only used for a live capture, but it doesn't
1728 * hurt to check it here.
1730 capture_in_progress
= captureSession()->state
!= CAPTURE_STOPPED
;
1734 if (prefs
.gui_ask_unsaved
) {
1735 if (cf_has_unsaved_data(capture_file_
.capFile())) {
1736 if (context
== Update
) {
1737 // We're being called from the software update window;
1738 // don't spawn yet another dialog. Just try again later.
1739 // XXX: The WinSparkle dialogs *aren't* modal, and a user
1740 // can bring Stratoshark to the foreground, close/save the
1741 // file, and then click "Install Update" again, but it
1742 // seems like many users don't expect that (and also don't
1743 // know that Help->Check for Updates... exist, only knowing
1744 // about the automatic check.) See #17658 and duplicates.
1745 // Maybe we *should* spawn the dialog?
1749 QMessageBox msg_dialog
;
1752 QPushButton
*save_button
;
1753 QPushButton
*discard_button
;
1755 msg_dialog
.setIcon(QMessageBox::Question
);
1756 msg_dialog
.setWindowTitle("Unsaved packets" UTF8_HORIZONTAL_ELLIPSIS
);
1758 /* This file has unsaved data or there's a capture in
1759 progress; ask the user whether to save the data. */
1760 if (capture_in_progress
&& context
!= Restart
) {
1761 question
= tr("Do you want to stop the capture and save the captured packets%1?").arg(before_what
);
1762 infotext
= tr("Your captured packets will be lost if you don't save them.");
1763 } else if (capture_file_
.capFile()->is_tempfile
) {
1764 if (context
== Reload
) {
1765 // Reloading a tempfile will keep the packets, so this is not unsaved packets
1766 question
= tr("Do you want to save the changes you've made%1?").arg(before_what
);
1767 infotext
= tr("Your changes will be lost if you don't save them.");
1769 question
= tr("Do you want to save the captured packets%1?").arg(before_what
);
1770 infotext
= tr("Your captured packets will be lost if you don't save them.");
1773 // No capture in progress and not a tempfile, so this is not unsaved packets
1774 char *display_basename
= g_filename_display_basename(capture_file_
.capFile()->filename
);
1775 question
= tr("Do you want to save the changes you've made to the capture file \"%1\"%2?").arg(display_basename
, before_what
);
1776 infotext
= tr("Your changes will be lost if you don't save them.");
1777 g_free(display_basename
);
1780 msg_dialog
.setText(question
);
1781 msg_dialog
.setInformativeText(infotext
);
1783 // XXX Text comes from ui/gtk/stock_icons.[ch]
1784 // Note that the button roles differ from the GTK+ version.
1785 // Cancel = RejectRole
1786 // Save = AcceptRole
1787 // Don't Save = DestructiveRole
1788 msg_dialog
.addButton(QMessageBox::Cancel
);
1790 if (capture_in_progress
) {
1791 QString save_button_text
;
1792 if (context
== Restart
) {
1793 save_button_text
= tr("Save before Continue");
1795 save_button_text
= tr("Stop and Save");
1797 save_button
= msg_dialog
.addButton(save_button_text
, QMessageBox::AcceptRole
);
1799 save_button
= msg_dialog
.addButton(QMessageBox::Save
);
1801 msg_dialog
.setDefaultButton(save_button
);
1803 QString discard_button_text
;
1804 if (capture_in_progress
) {
1807 discard_button_text
= tr("Stop and Quit &without Saving");
1810 discard_button_text
= tr("Continue &without Saving");
1813 discard_button_text
= tr("Stop and Continue &without Saving");
1819 discard_button_text
= tr("Quit &without Saving");
1823 discard_button_text
= tr("Continue &without Saving");
1827 discard_button
= msg_dialog
.addButton(discard_button_text
, QMessageBox::DestructiveRole
);
1829 #if defined(Q_OS_MAC)
1831 * In macOS, the "default button" is not necessarily the
1832 * button that has the input focus; Enter/Return activates
1833 * the default button, and the spacebar activates the button
1834 * that has the input focus, and they might be different
1837 * In a "do you want to save" dialog, for example, the
1838 * "save" button is the default button, and the "don't
1839 * save" button has the input focus, so you can press
1840 * Enter/Return to save or space not to save (or Escape
1841 * to dismiss the dialog).
1843 * In Qt terms, this means "no auto-default", as auto-default
1844 * makes the button with the input focus the default button,
1845 * so that Enter/Return will activate it.
1847 QList
<QAbstractButton
*> buttons
= msg_dialog
.buttons();
1848 for (int i
= 0; i
< buttons
.size(); ++i
) {
1849 QPushButton
*button
= static_cast<QPushButton
*>(buttons
.at(i
));
1850 button
->setAutoDefault(false);
1854 * It also means that the "don't save" button should be the one
1855 * initially given the focus.
1857 discard_button
->setFocus();
1860 * On Windows, if multiple Wireshark processes are open, another
1861 * application has focus, and "Close all [Wireshark] windows" is
1862 * chosen from the taskbar, we need to activate the window to
1863 * at least flash the taskbar (#16309).
1867 /* According to the Qt doc:
1868 * when using QMessageBox with custom buttons, exec() function returns an opaque value.
1870 * Therefore we should use clickedButton() to determine which button was clicked. */
1872 if (msg_dialog
.clickedButton() == save_button
) {
1874 /* If there's a capture in progress, we have to stop the capture
1875 and then do the save. */
1876 if (capture_in_progress
)
1879 /* Save the file and close it */
1880 // XXX if no packets were captured, any unsaved comments set by
1881 // the user are silently discarded because capFile() is null.
1882 if (capture_file_
.capFile() && saveCaptureFile(capture_file_
.capFile(), true) == false)
1884 do_close_file
= true;
1885 } else if (msg_dialog
.clickedButton() == discard_button
) {
1886 /* Just close the file, discarding changes */
1887 do_close_file
= true;
1889 // cancelButton or some other unspecified button
1893 /* Unchanged file or capturing with no packets */
1894 do_close_file
= true;
1897 /* User asked not to be bothered by those prompts, just close it.
1898 XXX - should that apply only to saving temporary files? */
1899 do_close_file
= true;
1903 * Are we done with this file and should we close the file?
1905 if (do_close_file
) {
1907 /* If there's a capture in progress, we have to stop the capture
1908 and then do the close. */
1909 if (capture_in_progress
)
1911 else if (capture_file_
.capFile() && capture_file_
.capFile()->state
== FILE_READ_IN_PROGRESS
) {
1913 * When an offline capture is being read, mark it as aborted.
1914 * cf_read will be responsible for actually closing the capture.
1916 * We cannot just invoke cf_close here since cf_read is up in the
1917 * call chain. (update_progress_dlg can end up processing the Quit
1918 * event from the user which then ends up here.)
1919 * See also the above "read_lock" check.
1921 capture_file_
.capFile()->state
= FILE_READ_ABORTED
;
1925 /* Clear MainWindow file name details */
1926 gbl_cur_main_window_
->setMwFileName("");
1928 /* captureStop() will close the file if not having any packets */
1929 if (capture_file_
.capFile() && context
!= Restart
&& context
!= Reload
)
1930 // Don't really close if Restart or Reload
1931 cf_close(capture_file_
.capFile());
1934 return true; /* File closed */
1937 void StratosharkMainWindow::captureStop() {
1940 while (capture_file_
.capFile() && (capture_file_
.capFile()->state
== FILE_READ_IN_PROGRESS
||
1941 capture_file_
.capFile()->state
== FILE_READ_PENDING
)) {
1942 WiresharkApplication::processEvents();
1946 void StratosharkMainWindow::findTextCodecs() {
1947 const QList
<int> mibs
= QTextCodec::availableMibs();
1948 QRegularExpression
ibmRegExp("^IBM([0-9]+).*$");
1949 QRegularExpression
iso8859RegExp("^ISO-8859-([0-9]+).*$");
1950 QRegularExpression
windowsRegExp("^WINDOWS-([0-9]+).*$");
1951 QRegularExpressionMatch match
;
1952 for (int mib
: mibs
) {
1953 QTextCodec
*codec
= QTextCodec::codecForMib(mib
);
1954 // QTextCodec::availableMibs() returns a list of hard-coded MIB
1955 // numbers, it doesn't check if they are really available. ICU data may
1956 // not have been compiled with support for all encodings.
1961 QString key
= codec
->name().toUpper();
1964 if (key
.localeAwareCompare("IBM") < 0) {
1966 } else if ((match
= ibmRegExp
.match(key
)).hasMatch()) {
1967 rank
= match
.captured(1).size(); // Up to 5
1968 } else if (key
.localeAwareCompare("ISO-8859-") < 0) {
1970 } else if ((match
= iso8859RegExp
.match(key
)).hasMatch()) {
1971 rank
= 6 + match
.captured(1).size(); // Up to 6 + 2
1972 } else if (key
.localeAwareCompare("WINDOWS-") < 0) {
1974 } else if ((match
= windowsRegExp
.match(key
)).hasMatch()) {
1975 rank
= 9 + match
.captured(1).size(); // Up to 9 + 4
1979 // This doesn't perfectly well order the IBM codecs because it's
1980 // annoying to properly place IBM00858 and IBM00924 in the middle of
1981 // code page numbers not zero padded to 5 digits.
1982 // We could manipulate the key further to have more commonly used
1983 // charsets earlier. IANA MIB ordering would be unexpected:
1984 // https://www.iana.org/assignments/character-sets/character-sets.xml
1985 // For data about use in HTTP (other protocols can be quite different):
1986 // https://w3techs.com/technologies/overview/character_encoding
1988 key
.prepend(char('0' + rank
));
1989 // We use a map here because, due to backwards compatibility,
1990 // the same QTextCodec may be returned for multiple MIBs, which
1991 // happens for GBK/GB2312, EUC-KR/windows-949/UHC, and others.
1992 text_codec_map_
.insert(key
, codec
);
1996 void StratosharkMainWindow::initMainToolbarIcons()
1998 // Normally 16 px. Reflects current GTK+ behavior and other Windows apps.
1999 int icon_size
= style()->pixelMetric(QStyle::PM_SmallIconSize
);
2000 #if !defined(Q_OS_WIN)
2001 // Force icons to 24x24 for now, otherwise actionFileOpen looks wonky.
2002 // The macOS HIG specifies 32-pixel icons but they're a little too
2004 icon_size
= icon_size
* 3 / 2;
2006 main_ui_
->mainToolBar
->setIconSize(QSize(icon_size
, icon_size
));
2008 // Toolbar actions. The GNOME HIG says that we should have a menu icon for each
2009 // toolbar item but that clutters up our menu. Set menu icons sparingly.
2011 main_ui_
->actionCaptureStart
->setIcon(StockIcon("x-capture-start-circle"));
2012 main_ui_
->actionCaptureStop
->setIcon(StockIcon("x-capture-stop"));
2013 main_ui_
->actionCaptureRestart
->setIcon(StockIcon("x-capture-restart-circle"));
2014 main_ui_
->actionCaptureOptions
->setIcon(StockIcon("x-capture-options"));
2016 // Menu icons are disabled in stratoshark_main_window.ui for these File-> items.
2017 main_ui_
->actionFileOpen
->setIcon(StockIcon("document-open"));
2018 main_ui_
->actionFileSave
->setIcon(StockIcon("x-capture-file-save"));
2019 main_ui_
->actionFileClose
->setIcon(StockIcon("x-capture-file-close"));
2021 main_ui_
->actionEditFindPacket
->setIcon(StockIcon("edit-find"));
2022 main_ui_
->actionGoPreviousPacket
->setIcon(StockIcon("go-previous"));
2023 main_ui_
->actionGoNextPacket
->setIcon(StockIcon("go-next"));
2024 main_ui_
->actionGoGoToPacket
->setIcon(StockIcon("go-jump"));
2025 main_ui_
->actionGoFirstPacket
->setIcon(StockIcon("go-first"));
2026 main_ui_
->actionGoLastPacket
->setIcon(StockIcon("go-last"));
2027 main_ui_
->actionGoPreviousConversationPacket
->setIcon(StockIcon("go-previous"));
2028 main_ui_
->actionGoNextConversationPacket
->setIcon(StockIcon("go-next"));
2029 #if defined(Q_OS_MAC)
2030 main_ui_
->actionGoPreviousConversationPacket
->setShortcut(QKeySequence(Qt::META
| Qt::Key_Comma
));
2031 main_ui_
->actionGoNextConversationPacket
->setShortcut(QKeySequence(Qt::META
| Qt::Key_Period
));
2033 main_ui_
->actionGoPreviousHistoryPacket
->setIcon(StockIcon("go-previous"));
2034 main_ui_
->actionGoNextHistoryPacket
->setIcon(StockIcon("go-next"));
2035 main_ui_
->actionGoAutoScroll
->setIcon(StockIcon("x-stay-last"));
2037 main_ui_
->actionViewColorizePacketList
->setIcon(StockIcon("x-colorize-packets"));
2039 QList
<QKeySequence
> zi_seq
= main_ui_
->actionViewZoomIn
->shortcuts();
2040 zi_seq
<< QKeySequence(Qt::CTRL
| Qt::Key_Equal
);
2041 main_ui_
->actionViewZoomIn
->setIcon(StockIcon("zoom-in"));
2042 main_ui_
->actionViewZoomIn
->setShortcuts(zi_seq
);
2043 main_ui_
->actionViewZoomOut
->setIcon(StockIcon("zoom-out"));
2044 main_ui_
->actionViewNormalSize
->setIcon(StockIcon("zoom-original"));
2045 main_ui_
->actionViewResizeColumns
->setIcon(StockIcon("x-resize-columns"));
2046 main_ui_
->actionViewResetLayout
->setIcon(StockIcon("x-reset-layout_2"));
2047 main_ui_
->actionViewReload
->setIcon(StockIcon("x-capture-file-reload"));
2049 main_ui_
->actionNewDisplayFilterExpression
->setIcon(StockIcon("list-add"));
2052 void StratosharkMainWindow::initShowHideMainWidgets()
2054 if (show_hide_actions_
) {
2058 show_hide_actions_
= new QActionGroup(this);
2059 QMap
<QAction
*, QWidget
*> shmw_actions
;
2061 show_hide_actions_
->setExclusive(false);
2062 shmw_actions
[main_ui_
->actionViewMainToolbar
] = main_ui_
->mainToolBar
;
2063 shmw_actions
[main_ui_
->actionViewFilterToolbar
] = main_ui_
->displayFilterToolBar
;
2064 shmw_actions
[main_ui_
->actionViewStatusBar
] = main_ui_
->statusBar
;
2065 shmw_actions
[main_ui_
->actionViewPacketList
] = packet_list_
;
2066 shmw_actions
[main_ui_
->actionViewPacketDetails
] = proto_tree_
;
2067 shmw_actions
[main_ui_
->actionViewPacketBytes
] = byte_view_tab_
;
2069 foreach(QAction
*shmwa
, shmw_actions
.keys()) {
2070 shmwa
->setData(QVariant::fromValue(shmw_actions
[shmwa
]));
2071 show_hide_actions_
->addAction(shmwa
);
2074 // Initial hide the Interface Toolbar submenu
2075 main_ui_
->menuInterfaceToolbars
->menuAction()->setVisible(false);
2077 /* Initially hide the additional toolbars menus */
2078 main_ui_
->menuAdditionalToolbars
->menuAction()->setVisible(false);
2080 connect(show_hide_actions_
, &QActionGroup::triggered
, this, &StratosharkMainWindow::showHideMainWidgets
);
2083 void StratosharkMainWindow::initTimeDisplayFormatMenu()
2085 if (time_display_actions_
) {
2089 time_display_actions_
= new QActionGroup(this);
2091 td_actions
[main_ui_
->actionViewTimeDisplayFormatDateYMDandTimeOfDay
] = TS_ABSOLUTE_WITH_YMD
;
2092 td_actions
[main_ui_
->actionViewTimeDisplayFormatDateYDOYandTimeOfDay
] = TS_ABSOLUTE_WITH_YDOY
;
2093 td_actions
[main_ui_
->actionViewTimeDisplayFormatTimeOfDay
] = TS_ABSOLUTE
;
2094 td_actions
[main_ui_
->actionViewTimeDisplayFormatSecondsSinceEpoch
] = TS_EPOCH
;
2095 td_actions
[main_ui_
->actionViewTimeDisplayFormatSecondsSinceBeginningOfCapture
] = TS_RELATIVE
;
2096 td_actions
[main_ui_
->actionViewTimeDisplayFormatSecondsSincePreviousCapturedPacket
] = TS_DELTA
;
2097 td_actions
[main_ui_
->actionViewTimeDisplayFormatSecondsSincePreviousDisplayedPacket
] = TS_DELTA_DIS
;
2098 td_actions
[main_ui_
->actionViewTimeDisplayFormatUTCDateYMDandTimeOfDay
] = TS_UTC_WITH_YMD
;
2099 td_actions
[main_ui_
->actionViewTimeDisplayFormatUTCDateYDOYandTimeOfDay
] = TS_UTC_WITH_YDOY
;
2100 td_actions
[main_ui_
->actionViewTimeDisplayFormatUTCTimeOfDay
] = TS_UTC
;
2102 foreach(QAction
* tda
, td_actions
.keys()) {
2103 tda
->setData(QVariant::fromValue(td_actions
[tda
]));
2104 time_display_actions_
->addAction(tda
);
2107 connect(time_display_actions_
, &QActionGroup::triggered
, this, &StratosharkMainWindow::setTimestampFormat
);
2110 void StratosharkMainWindow::initTimePrecisionFormatMenu()
2112 if (time_precision_actions_
) {
2116 time_precision_actions_
= new QActionGroup(this);
2118 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecisionAutomatic
] = TS_PREC_AUTO
;
2119 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecisionSeconds
] = TS_PREC_FIXED_SEC
;
2120 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecision100Milliseconds
] = TS_PREC_FIXED_100_MSEC
;
2121 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecision10Milliseconds
] = TS_PREC_FIXED_10_MSEC
;
2122 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecisionMilliseconds
] = TS_PREC_FIXED_MSEC
;
2123 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecision100Microseconds
] = TS_PREC_FIXED_100_USEC
;
2124 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecision10Microseconds
] = TS_PREC_FIXED_10_USEC
;
2125 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecisionMicroseconds
] = TS_PREC_FIXED_USEC
;
2126 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecision100Nanoseconds
] = TS_PREC_FIXED_100_NSEC
;
2127 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecision10Nanoseconds
] = TS_PREC_FIXED_10_NSEC
;
2128 tp_actions
[main_ui_
->actionViewTimeDisplayFormatPrecisionNanoseconds
] = TS_PREC_FIXED_NSEC
;
2130 foreach(QAction
* tpa
, tp_actions
.keys()) {
2131 tpa
->setData(QVariant::fromValue(tp_actions
[tpa
]));
2132 time_precision_actions_
->addAction(tpa
);
2135 connect(time_precision_actions_
, &QActionGroup::triggered
, this, &StratosharkMainWindow::setTimestampPrecision
);
2138 // Menu items which will be disabled when we freeze() and whose state will
2139 // be restored when we thaw(). Add to the list as needed.
2140 void StratosharkMainWindow::initFreezeActions()
2142 QList
<QAction
*> freeze_actions
= QList
<QAction
*>()
2143 << main_ui_
->actionFileClose
2144 << main_ui_
->actionViewReload
2145 << main_ui_
->actionEditMarkSelected
2146 << main_ui_
->actionEditMarkAllDisplayed
2147 << main_ui_
->actionEditUnmarkAllDisplayed
2148 << main_ui_
->actionEditIgnoreSelected
2149 << main_ui_
->actionEditIgnoreAllDisplayed
2150 << main_ui_
->actionEditUnignoreAllDisplayed
2151 << main_ui_
->actionEditSetTimeReference
2152 << main_ui_
->actionEditUnsetAllTimeReferences
;
2154 foreach(QAction
*action
, freeze_actions
) {
2155 freeze_actions_
<< QPair
<QAction
*, bool>(action
, false);
2159 void StratosharkMainWindow::initConversationMenus()
2163 QList
<QAction
*> cc_actions
= QList
<QAction
*>()
2164 << main_ui_
->actionViewColorizeConversation1
<< main_ui_
->actionViewColorizeConversation2
2165 << main_ui_
->actionViewColorizeConversation3
<< main_ui_
->actionViewColorizeConversation4
2166 << main_ui_
->actionViewColorizeConversation5
<< main_ui_
->actionViewColorizeConversation6
2167 << main_ui_
->actionViewColorizeConversation7
<< main_ui_
->actionViewColorizeConversation8
2168 << main_ui_
->actionViewColorizeConversation9
<< main_ui_
->actionViewColorizeConversation10
;
2170 packet_list_
->conversationMenu()->clear();
2171 packet_list_
->colorizeMenu()->clear();
2172 proto_tree_
->colorizeMenu()->clear();
2173 main_ui_
->menuConversationFilter
->clear();
2175 for (GList
*conv_filter_list_entry
= log_conv_filter_list
; conv_filter_list_entry
; conv_filter_list_entry
= gxx_list_next(conv_filter_list_entry
)) {
2177 conversation_filter_t
* conv_filter
= gxx_list_data(conversation_filter_t
*, conv_filter_list_entry
);
2178 ConversationAction
*conv_action
= new ConversationAction(main_ui_
->menuConversationFilter
, conv_filter
);
2179 main_ui_
->menuConversationFilter
->addAction(conv_action
);
2181 connect(this, &StratosharkMainWindow::packetInfoChanged
, conv_action
, &ConversationAction::setPacketInfo
);
2182 connect(conv_action
, &ConversationAction::triggered
, this, &StratosharkMainWindow::applyConversationFilter
, Qt::QueuedConnection
);
2184 // Packet list context menu items
2185 packet_list_
->conversationMenu()->addAction(conv_action
);
2187 QMenu
*submenu
= packet_list_
->colorizeMenu()->addMenu(conv_action
->text());
2190 foreach(QAction
*cc_action
, cc_actions
) {
2191 conv_action
= new ConversationAction(submenu
, conv_filter
);
2192 conv_action
->setText(cc_action
->text());
2193 conv_action
->setIcon(cc_action
->icon());
2194 conv_action
->setColorNumber(i
++);
2195 submenu
->addAction(conv_action
);
2196 connect(this, &StratosharkMainWindow::packetInfoChanged
, conv_action
, &ConversationAction::setPacketInfo
);
2197 connect(conv_action
, &ConversationAction::triggered
, this, &StratosharkMainWindow::colorizeActionTriggered
);
2200 conv_action
= new ConversationAction(submenu
, conv_filter
);
2201 conv_action
->setText(main_ui_
->actionViewColorizeNewColoringRule
->text());
2202 submenu
->addAction(conv_action
);
2203 connect(this, &StratosharkMainWindow::packetInfoChanged
, conv_action
, &ConversationAction::setPacketInfo
);
2204 connect(conv_action
, &ConversationAction::triggered
, this, &StratosharkMainWindow::colorizeActionTriggered
);
2206 // Proto tree conversation menu is filled in in ProtoTree::contextMenuEvent.
2207 // We should probably do that here.
2210 // Proto tree colorization items
2212 ColorizeAction
*colorize_action
;
2213 foreach(QAction
*cc_action
, cc_actions
) {
2214 colorize_action
= new ColorizeAction(proto_tree_
->colorizeMenu());
2215 colorize_action
->setText(cc_action
->text());
2216 colorize_action
->setIcon(cc_action
->icon());
2217 colorize_action
->setColorNumber(i
++);
2218 proto_tree_
->colorizeMenu()->addAction(colorize_action
);
2219 connect(this, &StratosharkMainWindow::fieldFilterChanged
, colorize_action
, &ColorizeAction::setFieldFilter
);
2220 connect(colorize_action
, &ColorizeAction::triggered
, this, &StratosharkMainWindow::colorizeActionTriggered
);
2223 colorize_action
= new ColorizeAction(proto_tree_
->colorizeMenu());
2224 colorize_action
->setText(main_ui_
->actionViewColorizeNewColoringRule
->text());
2225 proto_tree_
->colorizeMenu()->addAction(colorize_action
);
2226 connect(this, &StratosharkMainWindow::fieldFilterChanged
, colorize_action
, &ColorizeAction::setFieldFilter
);
2227 connect(colorize_action
, &ColorizeAction::triggered
, this, &StratosharkMainWindow::colorizeActionTriggered
);
2230 bool StratosharkMainWindow::addFollowStreamMenuItem(const void *key _U_
, void *value
, void *userdata
)
2232 register_follow_t
*follow
= (register_follow_t
*)value
;
2233 StratosharkMainWindow
*window
= (StratosharkMainWindow
*)userdata
;
2235 FollowStreamAction
*follow_action
= new FollowStreamAction(window
->main_ui_
->menuFollow
, follow
);
2236 window
->main_ui_
->menuFollow
->addAction(follow_action
);
2238 follow_action
->setEnabled(false);
2240 /* Special features for some of the built in follow types, like
2241 * shortcuts and overriding the name. XXX: Should these go in
2242 * FollowStreamAction, or should some of these (e.g. TCP and UDP)
2243 * be registered in initFollowStreamMenus so that they can be
2244 * on the top of the menu list too?
2246 // XXX - Should we add matches for syscall properties, e.g. file descriptors?
2247 const char *short_name
= (const char*)key
;
2248 if (g_strcmp0(short_name
, "Falco Bridge") == 0) {
2249 follow_action
->setText(tr("File Descriptor Stream"));
2251 // if (g_strcmp0(short_name, "TCP") == 0) {
2252 // follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_T);
2253 // } else if (g_strcmp0(short_name, "UDP") == 0) {
2254 // follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_U);
2255 // } else if (g_strcmp0(short_name, "DCCP") == 0) {
2256 // /* XXX: Not sure this one is widely enough used to need a shortcut. */
2257 // follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_E);
2258 // } else if (g_strcmp0(short_name, "TLS") == 0) {
2259 // follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_S);
2260 // } else if (g_strcmp0(short_name, "HTTP") == 0) {
2261 // follow_action->setShortcut(Qt::CTRL | Qt::ALT | Qt::SHIFT | Qt::Key_H);
2262 // } else if (g_strcmp0(short_name, "HTTP2") == 0) {
2263 // follow_action->setText(tr("HTTP/2 Stream"));
2264 // } else if (g_strcmp0(short_name, "SIP") == 0) {
2265 // follow_action->setText(tr("SIP Call"));
2266 // } else if (g_strcmp0(short_name, "USBCOM") == 0) {
2267 // follow_action->setText(tr("USB CDC Data"));
2270 connect(follow_action
, &QAction::triggered
, window
,
2271 [window
, follow
]() { window
->openFollowStreamDialog(get_follow_proto_id(follow
)); },
2272 Qt::QueuedConnection
);
2276 void StratosharkMainWindow::initFollowStreamMenus()
2278 /* This puts them all in the menus in alphabetical order. */
2279 follow_iterate_followers(addFollowStreamMenuItem
, this);
2283 void StratosharkMainWindow::setTitlebarForCaptureFile()
2285 use_capturing_title_
= false;
2289 QString
StratosharkMainWindow::replaceWindowTitleVariables(QString title
)
2291 title
.replace("%P", get_profile_name());
2292 title
.replace("%V", get_ss_vcs_version_info());
2295 if (global_commandline_info
.capture_comments
) {
2296 // Use the first capture comment from command line.
2297 title
.replace("%C", (char *)g_ptr_array_index(global_commandline_info
.capture_comments
, 0));
2299 // No capture comment.
2306 if (title
.contains("%F")) {
2307 // %F is file path of the capture file.
2308 if (capture_file_
.capFile()) {
2309 // get_dirname() will overwrite the argument so make a copy first
2310 char *filename
= g_strdup(capture_file_
.capFile()->filename
);
2311 QString
file(get_dirname(filename
));
2314 // Substitute HOME with ~
2315 QString
homedir(g_getenv("HOME"));
2316 if (!homedir
.isEmpty()) {
2317 homedir
.remove(QRegularExpression("[/]+$"));
2318 file
.replace(homedir
, "~");
2321 title
.replace("%F", file
);
2323 // No file loaded, no folder name
2328 if (title
.contains("%S")) {
2329 // %S is a conditional separator (" - ") that only shows when surrounded by variables
2330 // with values or static text. Remove repeating, leading and trailing separators.
2331 title
.replace(QRegularExpression("(%S)+"), "%S");
2332 title
.remove(QRegularExpression("^%S|%S$"));
2334 // On macOS we separate with a unicode em dash
2335 title
.replace("%S", " " UTF8_EM_DASH
" ");
2337 title
.replace("%S", " - ");
2344 void StratosharkMainWindow::setWSWindowTitle(QString title
)
2346 if (title
.isEmpty()) {
2347 title
= tr("The Stratoshark System Call and Log Analyzer");
2350 if (prefs
.gui_prepend_window_title
&& prefs
.gui_prepend_window_title
[0]) {
2351 QString custom_title
= replaceWindowTitleVariables(prefs
.gui_prepend_window_title
);
2352 if (custom_title
.length() > 0) {
2353 title
.prepend(QStringLiteral("[%1] ").arg(custom_title
));
2357 if (prefs
.gui_window_title
&& prefs
.gui_window_title
[0]) {
2358 QString custom_title
= replaceWindowTitleVariables(prefs
.gui_window_title
);
2359 if (custom_title
.length() > 0) {
2361 // On macOS we separate the titles with a unicode em dash
2362 title
.append(QStringLiteral(" %1 %2").arg(UTF8_EM_DASH
).arg(custom_title
));
2364 title
.append(QStringLiteral(" [%1]").arg(custom_title
));
2369 setWindowTitle(title
);
2370 setWindowFilePath(NULL
);
2373 void StratosharkMainWindow::setTitlebarForCaptureInProgress()
2375 use_capturing_title_
= true;
2379 void StratosharkMainWindow::updateTitlebar()
2381 if (use_capturing_title_
&& capture_file_
.capFile()) {
2382 setWSWindowTitle(tr("Capturing from %1").arg(cf_get_tempfile_source(capture_file_
.capFile())));
2383 } else if (capture_file_
.capFile() && capture_file_
.capFile()->filename
) {
2384 setWSWindowTitle(QStringLiteral("[*]%1").arg(capture_file_
.fileDisplayName()));
2386 // XXX - on non-Mac platforms, put in the application
2387 // name? Or do so only for temporary files?
2389 if (!capture_file_
.capFile()->is_tempfile
) {
2391 // Set the file path; that way, for macOS, it'll set the
2394 setWindowFilePath(capture_file_
.filePath());
2396 setWindowModified(cf_has_unsaved_data(capture_file_
.capFile()));
2398 /* We have no capture file. */
2405 /* Enable or disable menu items based on whether you have a capture file
2406 you've finished reading and, if you have one, whether it's been saved
2407 and whether it could be saved except by copying the raw packet data. */
2408 void StratosharkMainWindow::setMenusForCaptureFile(bool force_disable
)
2411 bool can_write
= false;
2412 bool can_save
= false;
2413 bool can_save_as
= false;
2415 if (force_disable
|| capture_file_
.capFile() == NULL
|| capture_file_
.capFile()->state
== FILE_READ_IN_PROGRESS
|| capture_file_
.capFile()->state
== FILE_READ_PENDING
) {
2416 /* We have no capture file or we're currently reading a file */
2419 /* We have a capture file. Can we write or save? */
2420 can_write
= cf_can_write_with_wiretap(capture_file_
.capFile());
2421 can_save
= cf_can_save(capture_file_
.capFile());
2422 can_save_as
= cf_can_save_as(capture_file_
.capFile());
2425 main_ui_
->actionViewReload_as_File_Format_or_Capture
->setEnabled(enable
);
2426 main_ui_
->actionFileMerge
->setEnabled(can_write
);
2427 main_ui_
->actionFileClose
->setEnabled(enable
);
2428 main_ui_
->actionFileSave
->setEnabled(can_save
);
2429 main_ui_
->actionFileSaveAs
->setEnabled(can_save_as
);
2430 main_ui_
->actionStatisticsCaptureFileProperties
->setEnabled(enable
);
2432 * "Export Specified Packets..." should be available only if
2433 * we can write the file out in at least one format.
2435 main_ui_
->actionFileExportPackets
->setEnabled(can_write
);
2437 main_ui_
->actionFileExportAsCArrays
->setEnabled(enable
);
2438 main_ui_
->actionFileExportAsCSV
->setEnabled(enable
);
2439 main_ui_
->actionFileExportAsPDML
->setEnabled(enable
);
2440 main_ui_
->actionFileExportAsPlainText
->setEnabled(enable
);
2441 main_ui_
->actionFileExportAsPSML
->setEnabled(enable
);
2442 main_ui_
->actionFileExportAsJSON
->setEnabled(enable
);
2444 main_ui_
->actionViewReload
->setEnabled(enable
);
2446 #ifdef HAVE_SOFTWARE_UPDATE
2447 // We might want to enable or disable automatic checks here as well.
2448 update_action_
->setEnabled(!can_save
);
2452 void StratosharkMainWindow::setMenusForCaptureInProgress(bool capture_in_progress
) {
2453 /* Either a capture was started or stopped; in either case, it's not
2454 in the process of stopping, so allow quitting. */
2456 main_ui_
->actionFileOpen
->setEnabled(!capture_in_progress
);
2457 main_ui_
->menuOpenRecentCaptureFile
->setEnabled(!capture_in_progress
);
2459 main_ui_
->actionFileExportAsCArrays
->setEnabled(capture_in_progress
);
2460 main_ui_
->actionFileExportAsCSV
->setEnabled(capture_in_progress
);
2461 main_ui_
->actionFileExportAsPDML
->setEnabled(capture_in_progress
);
2462 main_ui_
->actionFileExportAsPlainText
->setEnabled(capture_in_progress
);
2463 main_ui_
->actionFileExportAsPSML
->setEnabled(capture_in_progress
);
2464 main_ui_
->actionFileExportAsJSON
->setEnabled(capture_in_progress
);
2466 main_ui_
->menuFileSet
->setEnabled(!capture_in_progress
);
2467 main_ui_
->actionFileQuit
->setEnabled(true);
2468 #ifdef HAVE_SOFTWARE_UPDATE
2469 // We might want to enable or disable automatic checks here as well.
2470 update_action_
->setEnabled(!capture_in_progress
);
2473 main_ui_
->actionStatisticsCaptureFileProperties
->setEnabled(capture_in_progress
);
2475 // XXX Fix packet list heading menu sensitivity
2476 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortAscending",
2477 // !capture_in_progress);
2478 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortDescending",
2479 // !capture_in_progress);
2480 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/NoSorting",
2481 // !capture_in_progress);
2484 main_ui_
->actionCaptureOptions
->setEnabled(!capture_in_progress
);
2485 main_ui_
->actionCaptureStart
->setEnabled(!capture_in_progress
);
2486 main_ui_
->actionCaptureStart
->setChecked(capture_in_progress
);
2487 main_ui_
->actionCaptureStop
->setEnabled(capture_in_progress
);
2488 main_ui_
->actionCaptureRestart
->setEnabled(capture_in_progress
);
2489 main_ui_
->actionCaptureRefreshInterfaces
->setEnabled(!capture_in_progress
);
2490 #endif /* HAVE_LIBPCAP */
2494 void StratosharkMainWindow::setMenusForCaptureStopping() {
2495 main_ui_
->actionFileQuit
->setEnabled(false);
2496 #ifdef HAVE_SOFTWARE_UPDATE
2497 update_action_
->setEnabled(false);
2499 main_ui_
->actionStatisticsCaptureFileProperties
->setEnabled(false);
2501 main_ui_
->actionCaptureStart
->setChecked(false);
2502 main_ui_
->actionCaptureStop
->setEnabled(false);
2503 main_ui_
->actionCaptureRestart
->setEnabled(false);
2504 #endif /* HAVE_LIBPCAP */
2507 void StratosharkMainWindow::setForCapturedPackets(bool have_captured_packets
)
2509 main_ui_
->actionFilePrint
->setEnabled(have_captured_packets
);
2511 // set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/Print",
2512 // have_captured_packets);
2514 main_ui_
->actionEditFindPacket
->setEnabled(have_captured_packets
);
2515 main_ui_
->actionEditFindNext
->setEnabled(have_captured_packets
);
2516 main_ui_
->actionEditFindPrevious
->setEnabled(have_captured_packets
);
2518 main_ui_
->actionGoGoToPacket
->setEnabled(have_captured_packets
);
2519 main_ui_
->actionGoPreviousPacket
->setEnabled(have_captured_packets
);
2520 main_ui_
->actionGoNextPacket
->setEnabled(have_captured_packets
);
2521 main_ui_
->actionGoFirstPacket
->setEnabled(have_captured_packets
);
2522 main_ui_
->actionGoLastPacket
->setEnabled(have_captured_packets
);
2523 main_ui_
->actionGoNextConversationPacket
->setEnabled(have_captured_packets
);
2524 main_ui_
->actionGoPreviousConversationPacket
->setEnabled(have_captured_packets
);
2526 main_ui_
->actionViewZoomIn
->setEnabled(have_captured_packets
);
2527 main_ui_
->actionViewZoomOut
->setEnabled(have_captured_packets
);
2528 main_ui_
->actionViewNormalSize
->setEnabled(have_captured_packets
);
2529 main_ui_
->actionViewResizeColumns
->setEnabled(have_captured_packets
);
2531 main_ui_
->actionStatisticsCaptureFileProperties
->setEnabled(have_captured_packets
);
2532 main_ui_
->actionStatisticsProtocolHierarchy
->setEnabled(have_captured_packets
);
2533 main_ui_
->actionStatisticsIOGraph
->setEnabled(have_captured_packets
);
2536 void StratosharkMainWindow::setMenusForFileSet(bool enable_list_files
) {
2537 bool enable_next
= fileset_get_next() != NULL
&& enable_list_files
;
2538 bool enable_prev
= fileset_get_previous() != NULL
&& enable_list_files
;
2540 main_ui_
->actionFileSetListFiles
->setEnabled(enable_list_files
);
2541 main_ui_
->actionFileSetNextFile
->setEnabled(enable_next
);
2542 main_ui_
->actionFileSetPreviousFile
->setEnabled(enable_prev
);
2545 void StratosharkMainWindow::setWindowIcon(const QIcon
&icon
) {
2546 mainApp
->setWindowIcon(icon
);
2547 QMainWindow::setWindowIcon(icon
);
2550 void StratosharkMainWindow::updateForUnsavedChanges() {
2552 setMenusForCaptureFile();
2555 void StratosharkMainWindow::changeEvent(QEvent
* event
)
2559 switch (event
->type())
2561 case QEvent::LanguageChange
:
2562 main_ui_
->retranslateUi(this);
2563 // make sure that the "Clear Menu" item is retranslated
2564 mainApp
->emitAppSignal(WiresharkApplication::RecentCapturesChanged
);
2565 // make sure that the color actions in the PacketList and ProtoTree
2567 initConversationMenus();
2570 case QEvent::LocaleChange
: {
2571 QString locale
= QLocale::system().name();
2572 locale
.truncate(locale
.lastIndexOf('_'));
2573 mainApp
->loadLanguage(locale
);
2576 case QEvent::WindowStateChange
:
2577 main_ui_
->actionViewFullScreen
->setChecked(this->isFullScreen());
2583 QMainWindow::changeEvent(event
);
2586 /* Update main window items based on whether there's a capture in progress. */
2587 void StratosharkMainWindow::setForCaptureInProgress(bool capture_in_progress
, bool handle_toolbars
, GArray
*ifaces
)
2589 setMenusForCaptureInProgress(capture_in_progress
);
2592 packet_list_
->setCaptureInProgress(capture_in_progress
, main_ui_
->actionGoAutoScroll
->isChecked());
2594 // set_capture_if_dialog_for_capture_in_progress(capture_in_progress);
2597 if (handle_toolbars
) {
2598 QList
<InterfaceToolbar
*> toolbars
= findChildren
<InterfaceToolbar
*>();
2599 foreach(InterfaceToolbar
*toolbar
, toolbars
) {
2600 if (capture_in_progress
) {
2601 toolbar
->startCapture(ifaces
);
2603 toolbar
->stopCapture();
2609 void StratosharkMainWindow::addMenuActions(QList
<QAction
*> &actions
, int menu_group
)
2611 foreach(QAction
*action
, actions
) {
2612 switch (menu_group
) {
2613 case REGISTER_LOG_ANALYZE_GROUP_UNSORTED
:
2614 case REGISTER_LOG_STAT_GROUP_UNSORTED
:
2615 main_ui_
->menuStatistics
->insertAction(
2616 main_ui_
->actionStatistics_REGISTER_STAT_GROUP_UNSORTED
,
2619 case REGISTER_TOOLS_GROUP_UNSORTED
:
2621 main_ui_
->menuTools
->show(); // Remove this if we ever add any built-in tools.
2622 // Allow the creation of submenus. Mimics the behaviour of
2623 // ui/gtk/main_menubar.c:add_menu_item_to_main_menubar
2624 // and GtkUIManager.
2626 // For now we limit the insanity to the "Tools" menu.
2627 QStringList menu_path
= action
->text().split('/');
2628 QMenu
*cur_menu
= main_ui_
->menuTools
;
2629 while (menu_path
.length() > 1) {
2630 QString menu_title
= menu_path
.takeFirst();
2631 QMenu
*submenu
= cur_menu
->findChild
<QMenu
*>(menu_title
.toLower(), Qt::FindDirectChildrenOnly
);
2633 submenu
= cur_menu
->addMenu(menu_title
);
2634 submenu
->setObjectName(menu_title
.toLower());
2638 action
->setText(menu_path
.last());
2639 cur_menu
->addAction(action
);
2643 // Skip packet items.
2647 // Connect each action type to its corresponding slot. We to
2648 // distinguish various types of actions. Setting their objectName
2649 // seems to work OK.
2650 if (action
->objectName() == TapParameterDialog::actionName()) {
2651 connect(action
, &QAction::triggered
, this, [=]() { openTapParameterDialog(); });
2652 } else if (action
->objectName() == FunnelStatistics::actionName()) {
2653 connect(action
, &QAction::triggered
, funnel_statistics_
, &FunnelStatistics::funnelActionTriggered
);
2658 void StratosharkMainWindow::removeMenuActions(QList
<QAction
*> &actions
, int menu_group
)
2660 foreach(QAction
*action
, actions
) {
2661 switch (menu_group
) {
2662 case REGISTER_LOG_ANALYZE_GROUP_UNSORTED
:
2663 case REGISTER_LOG_STAT_GROUP_UNSORTED
:
2664 main_ui_
->menuStatistics
->removeAction(action
);
2666 case REGISTER_TOOLS_GROUP_UNSORTED
:
2668 // Allow removal of submenus.
2669 // For now we limit the insanity to the "Tools" menu.
2670 QStringList menu_path
= action
->text().split('/');
2671 QMenu
*cur_menu
= main_ui_
->menuTools
;
2672 while (menu_path
.length() > 1) {
2673 QString menu_title
= menu_path
.takeFirst();
2674 QMenu
*submenu
= cur_menu
->findChild
<QMenu
*>(menu_title
.toLower(), Qt::FindDirectChildrenOnly
);
2677 cur_menu
->removeAction(action
);
2678 // Remove empty submenus.
2679 while (cur_menu
!= main_ui_
->menuTools
) {
2680 QMenu
*empty_menu
= (cur_menu
->isEmpty() ? cur_menu
: NULL
);
2681 cur_menu
= dynamic_cast<QMenu
*>(cur_menu
->parent());
2687 // qDebug() << "FIX: Remove" << action->text() << "from the menu";
2693 void StratosharkMainWindow::addDynamicMenus()
2695 // Fill in each menu
2696 foreach(register_stat_group_t menu_group
, menu_groups_
) {
2697 QList
<QAction
*>actions
= mainApp
->dynamicMenuGroupItems(menu_group
);
2698 addMenuActions(actions
, menu_group
);
2702 void StratosharkMainWindow::reloadDynamicMenus()
2704 foreach(register_stat_group_t menu_group
, menu_groups_
) {
2705 QList
<QAction
*>actions
= mainApp
->removedMenuGroupItems(menu_group
);
2706 removeMenuActions(actions
, menu_group
);
2708 actions
= mainApp
->addedMenuGroupItems(menu_group
);
2709 addMenuActions(actions
, menu_group
);
2712 mainApp
->clearAddedMenuGroupItems();
2713 mainApp
->clearRemovedMenuGroupItems();
2716 void StratosharkMainWindow::externalMenuHelper(ext_menu_t
* menu
, QMenu
* subMenu
, int depth
)
2718 QAction
* itemAction
= Q_NULLPTR
;
2719 ext_menubar_t
* item
= Q_NULLPTR
;
2720 GList
* children
= Q_NULLPTR
;
2722 /* There must exists an xpath parent */
2723 Q_ASSERT(subMenu
!= NULL
);
2725 /* If the depth counter exceeds, something must have gone wrong */
2726 Q_ASSERT(depth
< EXT_MENUBAR_MAX_DEPTH
);
2728 children
= menu
->children
;
2729 /* Iterate the child entries */
2730 while (children
&& children
->data
) {
2731 item
= gxx_list_data(ext_menubar_t
*, children
);
2733 if (item
->type
== EXT_MENUBAR_MENU
) {
2734 /* Handle Submenu entry */
2735 this->externalMenuHelper(item
, subMenu
->addMenu(item
->label
), depth
++);
2736 } else if (item
->type
== EXT_MENUBAR_SEPARATOR
) {
2737 subMenu
->addSeparator();
2738 } else if (item
->type
== EXT_MENUBAR_ITEM
|| item
->type
== EXT_MENUBAR_URL
) {
2739 itemAction
= subMenu
->addAction(item
->name
);
2740 itemAction
->setData(QVariant::fromValue(static_cast<void *>(item
)));
2741 itemAction
->setText(item
->label
);
2742 connect(itemAction
, &QAction::triggered
, this, &StratosharkMainWindow::externalMenuItemTriggered
);
2746 children
= gxx_list_next(children
);
2750 QMenu
* StratosharkMainWindow::searchSubMenu(QString objectName
)
2754 if (objectName
.length() > 0) {
2755 QString searchName
= QStringLiteral("menu") + objectName
;
2757 lst
= main_ui_
->menuBar
->findChildren
<QMenu
*>();
2758 foreach(QMenu
* m
, lst
) {
2759 if (QString::compare(m
->objectName(), searchName
) == 0)
2767 void StratosharkMainWindow::addPluginIFStructures()
2769 GList
*user_menu
= ext_menubar_get_entries();
2771 while (user_menu
&& user_menu
->data
) {
2772 QMenu
*subMenu
= Q_NULLPTR
;
2773 ext_menu_t
*menu
= gxx_list_data(ext_menu_t
*, user_menu
);
2775 /* On this level only menu items should exist. Not doing an assert here,
2776 * as it could be an honest mistake */
2777 if (menu
->type
!= EXT_MENUBAR_MENU
) {
2778 user_menu
= gxx_list_next(user_menu
);
2782 /* Create main submenu and add it to the menubar */
2783 if (menu
->parent_menu
) {
2784 QMenu
*sortUnderneath
= searchSubMenu(QString(menu
->parent_menu
));
2786 subMenu
= sortUnderneath
->addMenu(menu
->label
);
2790 subMenu
= main_ui_
->menuBar
->addMenu(menu
->label
);
2792 /* This will generate the action structure for each menu. It is recursive,
2793 * therefore a sub-routine, and we have a depth counter to prevent endless loops. */
2794 this->externalMenuHelper(menu
, subMenu
, 0);
2797 user_menu
= gxx_list_next(user_menu
);
2800 int cntToolbars
= 0;
2802 QMenu
*tbMenu
= main_ui_
->menuAdditionalToolbars
;
2803 GList
*if_toolbars
= ext_toolbar_get_entries();
2804 while (if_toolbars
&& if_toolbars
->data
) {
2805 ext_toolbar_t
*toolbar
= gxx_list_data(ext_toolbar_t
*, if_toolbars
);
2807 if (toolbar
->type
!= EXT_TOOLBAR_BAR
) {
2808 if_toolbars
= gxx_list_next(if_toolbars
);
2812 bool visible
= g_list_find_custom(recent
.gui_additional_toolbars
, toolbar
->name
, reinterpret_cast<GCompareFunc
>(strcmp
)) ? true : false;
2814 AdditionalToolBar
*ifToolBar
= AdditionalToolBar::create(this, toolbar
);
2817 ifToolBar
->setVisible(visible
);
2819 QAction
*iftbAction
= new QAction(QString(toolbar
->name
), this);
2820 iftbAction
->setToolTip(toolbar
->tooltip
);
2821 iftbAction
->setEnabled(true);
2822 iftbAction
->setCheckable(true);
2823 iftbAction
->setChecked(visible
);
2824 iftbAction
->setToolTip(tr("Show or hide the toolbar"));
2825 iftbAction
->setData(VariantPointer
<ext_toolbar_t
>::asQVariant(toolbar
));
2827 QAction
*before
= Q_NULLPTR
;
2829 foreach(QAction
*action
, tbMenu
->actions()) {
2830 /* Ensure we add the menu entries in sorted order */
2831 if (action
->text().compare(toolbar
->name
, Qt::CaseInsensitive
) > 0) {
2837 tbMenu
->insertAction(before
, iftbAction
);
2839 addToolBar(Qt::TopToolBarArea
, ifToolBar
);
2840 insertToolBarBreak(ifToolBar
);
2842 if (show_hide_actions_
)
2843 show_hide_actions_
->addAction(iftbAction
);
2848 if_toolbars
= gxx_list_next(if_toolbars
);
2852 tbMenu
->menuAction()->setVisible(true);
2855 void StratosharkMainWindow::removeAdditionalToolbar(QString toolbarName
)
2857 if (toolbarName
.length() == 0)
2860 QList
<QToolBar
*> toolbars
= findChildren
<QToolBar
*>();
2861 foreach(QToolBar
*tb
, toolbars
) {
2862 AdditionalToolBar
*ifToolBar
= dynamic_cast<AdditionalToolBar
*>(tb
);
2864 if (ifToolBar
&& ifToolBar
->menuName().compare(toolbarName
)) {
2865 GList
*entry
= g_list_find_custom(recent
.gui_additional_toolbars
, qUtf8Printable(ifToolBar
->menuName()), reinterpret_cast<GCompareFunc
>(strcmp
));
2867 recent
.gui_additional_toolbars
= g_list_remove(recent
.gui_additional_toolbars
, entry
->data
);
2869 QList
<QAction
*> actions
= main_ui_
->menuAdditionalToolbars
->actions();
2870 foreach(QAction
*action
, actions
) {
2871 ext_toolbar_t
*item
= VariantPointer
<ext_toolbar_t
>::asPtr(action
->data());
2872 if (item
&& ifToolBar
->menuName().compare(item
->name
)) {
2873 if (show_hide_actions_
)
2874 show_hide_actions_
->removeAction(action
);
2875 main_ui_
->menuAdditionalToolbars
->removeAction(action
);
2884 QString
StratosharkMainWindow::getMwFileName()
2889 void StratosharkMainWindow::setMwFileName(QString fileName
)
2891 mwFileName_
= fileName
;