1 /* main_window.cpp
2 *
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "main_window.h"
11
12 /*
13 * The generated Ui_MainWindow::setupUi() can grow larger than our configured limit,
14 * so turn off -Wframe-larger-than= for ui_main_window.h.
15 */
16 DIAG_OFF(frame-larger-than=)
17 #include <ui_main_window.h>
18 DIAG_ON(frame-larger-than=)
19
20 #include <epan/addr_resolv.h>
21 #include "epan/conversation_filter.h"
22 #include <epan/epan_dissect.h>
23 #include <wsutil/filesystem.h>
24 #include <wsutil/wslog.h>
25 #include <wsutil/ws_assert.h>
26 #include <ui/version_info.h>
27 #include <epan/prefs.h>
28 #include <epan/stats_tree_priv.h>
29 #include <epan/plugin_if.h>
30 #include <epan/export_object.h>
31 #include <frame_tvbuff.h>
32
33 #include "ui/iface_toolbar.h"
34
35 #ifdef HAVE_LIBPCAP
36 #include "ui/capture.h"
37 #include <capture/capture_session.h>
38 #endif
39
40 #include "ui/alert_box.h"
41 #ifdef HAVE_LIBPCAP
42 #include "ui/capture_ui_utils.h"
43 #endif
44 #include "ui/capture_globals.h"
45 #include "ui/main_statusbar.h"
46 #include "ui/recent.h"
47 #include "ui/recent_utils.h"
48 #include "ui/util.h"
49 #include "ui/preference_utils.h"
50
51 #include "byte_view_tab.h"
52 #ifdef HAVE_LIBPCAP
53 #include "capture_options_dialog.h"
54 #endif
55 #include "conversation_colorize_action.h"
56 #include "export_dissection_dialog.h"
57 #include "export_object_action.h"
58 #include "file_set_dialog.h"
59 #include "filter_dialog.h"
60 #include "funnel_statistics.h"
61 #include "import_text_dialog.h"
62 #include "interface_toolbar.h"
63 #include "packet_diagram.h"
64 #include "packet_list.h"
65 #include "proto_tree.h"
66 #include "simple_dialog.h"
67 #include "tap_parameter_dialog.h"
68 #include "wireless_frame.h"
69 #include <ui/qt/widgets/wireless_timeline.h>
70 #include "wireshark_application.h"
71
72 #include <ui/qt/widgets/additional_toolbar.h>
73 #include <ui/qt/widgets/display_filter_edit.h>
74 #include <ui/qt/widgets/filter_expression_toolbar.h>
75
76 #include <ui/qt/utils/color_utils.h>
77 #include <ui/qt/utils/qt_ui_utils.h>
78 #include <ui/qt/utils/stock_icon.h>
79 #include <ui/qt/utils/variant_pointer.h>
80
81 #include <QAction>
82 #include <QActionGroup>
83 #include <QDesktopWidget>
84 #include <QKeyEvent>
85 #include <QList>
86 #include <QMessageBox>
87 #include <QMetaObject>
88 #include <QMimeData>
89 #include <QTabWidget>
90 #include <QTextCodec>
91 #include <QToolButton>
92 #include <QTreeWidget>
93 #include <QUrl>
94
95 //menu_recent_file_write_all
96
97 // If we ever add support for multiple windows this will need to be replaced.
98 static MainWindow *gbl_cur_main_window_ = NULL;
99
pipe_input_set_handler(gint source,gpointer user_data,ws_process_id * child_process,pipe_input_cb_t input_cb)100 void pipe_input_set_handler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
101 {
102 gbl_cur_main_window_->setPipeInputHandler(source, user_data, child_process, input_cb);
103 }
104
plugin_if_mainwindow_apply_filter(GHashTable * data_set)105 static void plugin_if_mainwindow_apply_filter(GHashTable * data_set)
106 {
107 if (!gbl_cur_main_window_ || !data_set)
108 return;
109
110 if (g_hash_table_lookup_extended(data_set, "filter_string", NULL, NULL)) {
111 QString filter((const char *)g_hash_table_lookup(data_set, "filter_string"));
112 gbl_cur_main_window_->filterPackets(filter);
113 }
114 }
115
plugin_if_mainwindow_preference(GHashTable * data_set)116 static void plugin_if_mainwindow_preference(GHashTable * data_set)
117 {
118 if (!gbl_cur_main_window_ || !data_set)
119 return;
120
121 const char * module_name;
122 const char * pref_name;
123 const char * pref_value;
124
125 DIAG_OFF_CAST_AWAY_CONST
126 if (g_hash_table_lookup_extended(data_set, "pref_module", NULL, (gpointer *)&module_name) &&
127 g_hash_table_lookup_extended(data_set, "pref_key", NULL, (gpointer *)&pref_name) &&
128 g_hash_table_lookup_extended(data_set, "pref_value", NULL, (gpointer *)&pref_value))
129 {
130 unsigned int changed_flags = prefs_store_ext(module_name, pref_name, pref_value);
131 if (changed_flags) {
132 wsApp->emitAppSignal(WiresharkApplication::PacketDissectionChanged);
133 wsApp->emitAppSignal(WiresharkApplication::PreferencesChanged);
134 }
135 }
136 DIAG_ON_CAST_AWAY_CONST
137 }
138
plugin_if_mainwindow_gotoframe(GHashTable * data_set)139 static void plugin_if_mainwindow_gotoframe(GHashTable * data_set)
140 {
141 if (!gbl_cur_main_window_ || !data_set)
142 return;
143
144 gpointer framenr;
145
146 if (g_hash_table_lookup_extended(data_set, "frame_nr", NULL, &framenr)) {
147 if (GPOINTER_TO_UINT(framenr) != 0)
148 gbl_cur_main_window_->gotoFrame(GPOINTER_TO_UINT(framenr));
149 }
150 }
151
152 #ifdef HAVE_LIBPCAP
153
plugin_if_mainwindow_get_ws_info(GHashTable * data_set)154 static void plugin_if_mainwindow_get_ws_info(GHashTable * data_set)
155 {
156 if (!gbl_cur_main_window_ || !data_set)
157 return;
158
159 ws_info_t *ws_info = NULL;
160
161 if (!g_hash_table_lookup_extended(data_set, "ws_info", NULL, (void**)&ws_info))
162 return;
163
164 CaptureFile *cfWrap = gbl_cur_main_window_->captureFile();
165 capture_file *cf = cfWrap->capFile();
166
167 ws_info->ws_info_supported = true;
168
169 /* If we have a filename attached to ws_info clear it */
170 if (ws_info->cf_filename != NULL)
171 {
172 g_free(ws_info->cf_filename);
173 ws_info->cf_filename = NULL;
174 }
175
176 /* Determine the true state of the capture file. We return the true state in
177 the ws_info structure and DON'T CHANGE the cf->state as we don't want to cause problems
178 with code that follows this. */
179 if (cf)
180 {
181 if (cf->filename)
182 {
183 /* As we have a cf->filename we'll use the name and the state */
184 ws_info->cf_filename = g_strdup(cf->filename);
185 ws_info->cf_state = cf->state;
186 }
187 else
188 {
189 /* When we come through here the cf->state can show FILE_READ_DONE even though the
190 file is actually closed (no filename). A better fix would be to have a
191 FILE_CLOSE_PENDING state but that involves a lot of code change elsewhere. */
192 ws_info->cf_state = FILE_CLOSED;
193 }
194 }
195
196 if (!ws_info->cf_filename)
197 {
198 /* We may have a filename associated with the main window so let's use it */
199 QString fileNameString = gbl_cur_main_window_->getMwFileName();
200 if (fileNameString.length())
201 {
202 QByteArray ba = fileNameString.toLatin1();
203 const char *c_file_name = ba.data();
204 ws_info->cf_filename = g_strdup(c_file_name);
205 }
206 }
207
208 if (cf) {
209 ws_info->cf_count = cf->count;
210
211 QList<int> rows = gbl_cur_main_window_->selectedRows();
212 frame_data * fdata = NULL;
213 if (rows.count() > 0)
214 fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0));
215
216 if (cf->state == FILE_READ_DONE && fdata) {
217 ws_info->cf_framenr = fdata->num;
218 ws_info->frame_passed_dfilter = (fdata->passed_dfilter == 1);
219 }
220 else {
221 ws_info->cf_framenr = 0;
222 ws_info->frame_passed_dfilter = FALSE;
223 }
224 }
225 else
226 {
227 /* Initialise the other ws_info structure values */
228 ws_info->cf_count = 0;
229 ws_info->cf_framenr = 0;
230 ws_info->frame_passed_dfilter = FALSE;
231 }
232 }
233
234 #endif /* HAVE_LIBPCAP */
235
plugin_if_mainwindow_get_frame_data(GHashTable * data_set)236 static void plugin_if_mainwindow_get_frame_data(GHashTable* data_set)
237 {
238 if (!gbl_cur_main_window_ || !data_set)
239 return;
240
241 plugin_if_frame_data_cb extract_cb;
242 void* user_data;
243 void** ret_value_ptr;
244
245 if (g_hash_table_lookup_extended(data_set, "extract_cb", NULL, (void**)&extract_cb) &&
246 g_hash_table_lookup_extended(data_set, "user_data", NULL, (void**)&user_data) &&
247 g_hash_table_lookup_extended(data_set, "ret_value_ptr", NULL, (void**)&ret_value_ptr))
248 {
249 QList<int> rows = gbl_cur_main_window_->selectedRows();
250 if (rows.count() > 0) {
251 frame_data* fdata = gbl_cur_main_window_->frameDataForRow(rows.at(0));
252 if (fdata) {
253 *ret_value_ptr = extract_cb(fdata, user_data);
254 }
255 }
256 }
257 }
258
plugin_if_mainwindow_get_capture_file(GHashTable * data_set)259 static void plugin_if_mainwindow_get_capture_file(GHashTable* data_set)
260 {
261 if (!gbl_cur_main_window_ || !data_set)
262 return;
263
264 plugin_if_capture_file_cb extract_cb;
265 void* user_data;
266 void** ret_value_ptr;
267
268 if (g_hash_table_lookup_extended(data_set, "extract_cb", NULL, (void**)&extract_cb) &&
269 g_hash_table_lookup_extended(data_set, "user_data", NULL, (void**)&user_data) &&
270 g_hash_table_lookup_extended(data_set, "ret_value_ptr", NULL, (void**)&ret_value_ptr))
271 {
272 CaptureFile* cfWrap = gbl_cur_main_window_->captureFile();
273 capture_file* cf = cfWrap->capFile();
274 if (cf) {
275 *ret_value_ptr = extract_cb(cf, user_data);
276 }
277 }
278 }
279
plugin_if_mainwindow_update_toolbars(GHashTable * data_set)280 static void plugin_if_mainwindow_update_toolbars(GHashTable * data_set)
281 {
282 if (!gbl_cur_main_window_ || !data_set)
283 return;
284
285 if (g_hash_table_lookup_extended(data_set, "toolbar_name", NULL, NULL)) {
286 QString toolbarName((const char *)g_hash_table_lookup(data_set, "toolbar_name"));
287 gbl_cur_main_window_->removeAdditionalToolbar(toolbarName);
288
289 }
290 }
291
mainwindow_add_toolbar(const iface_toolbar * toolbar_entry)292 static void mainwindow_add_toolbar(const iface_toolbar *toolbar_entry)
293 {
294 if (gbl_cur_main_window_ && toolbar_entry)
295 {
296 gbl_cur_main_window_->addInterfaceToolbar(toolbar_entry);
297 }
298 }
299
mainwindow_remove_toolbar(const gchar * menu_title)300 static void mainwindow_remove_toolbar(const gchar *menu_title)
301 {
302 if (gbl_cur_main_window_ && menu_title)
303 {
304 gbl_cur_main_window_->removeInterfaceToolbar(menu_title);
305 }
306 }
307
findOrAddMenu(QMenu * parent_menu,QString & menu_text)308 QMenu* MainWindow::findOrAddMenu(QMenu *parent_menu, QString& menu_text) {
309 QList<QAction *> actions = parent_menu->actions();
310 QList<QAction *>::const_iterator i;
311 for (i = actions.constBegin(); i != actions.constEnd(); ++i) {
312 if ((*i)->text()==menu_text) {
313 return (*i)->menu();
314 }
315 }
316 // If we get here there menu entry was not found, add a sub menu
317 return parent_menu->addMenu(menu_text);
318 }
319
MainWindow(QWidget * parent)320 MainWindow::MainWindow(QWidget *parent) :
321 QMainWindow(parent),
322 main_ui_(new Ui::MainWindow),
323 cur_layout_(QVector<unsigned>()),
324 packet_list_(NULL),
325 proto_tree_(NULL),
326 previous_focus_(NULL),
327 file_set_dialog_(NULL),
328 show_hide_actions_(NULL),
329 time_display_actions_(NULL),
330 time_precision_actions_(NULL),
331 funnel_statistics_(NULL),
332 freeze_focus_(NULL),
333 was_maximized_(false),
334 capture_stopping_(false),
335 capture_filter_valid_(false)
336 #ifdef HAVE_LIBPCAP
337 , capture_options_dialog_(NULL)
338 , info_data_()
339 #endif
340 , display_filter_dlg_(NULL)
341 , capture_filter_dlg_(NULL)
342 #ifdef _WIN32
343 , pipe_timer_(NULL)
344 #else
345 , pipe_notifier_(NULL)
346 #endif
347 #if defined(Q_OS_MAC)
348 , dock_menu_(NULL)
349 #endif
350 {
351 if (!gbl_cur_main_window_) {
352 connect(wsApp, SIGNAL(openStatCommandDialog(QString, const char*, void*)),
353 this, SLOT(openStatCommandDialog(QString, const char*, void*)));
354 connect(wsApp, SIGNAL(openTapParameterDialog(QString, const QString, void*)),
355 this, SLOT(openTapParameterDialog(QString, const QString, void*)));
356 }
357 gbl_cur_main_window_ = this;
358 #ifdef HAVE_LIBPCAP
359 capture_input_init(&cap_session_, CaptureFile::globalCapFile());
360 #endif
361
362 findTextCodecs();
363 // setpUi calls QMetaObject::connectSlotsByName(this). connectSlotsByName
364 // iterates over *all* of our children, looking for matching "on_" slots.
365 // The fewer children we have at this point the better.
366 main_ui_->setupUi(this);
367 #ifdef HAVE_SOFTWARE_UPDATE
368 update_action_ = new QAction(tr("Check for Updates…"), main_ui_->menuHelp);
369 #endif
370 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
371 wireless_frame_ = new WirelessFrame(this);
372 main_ui_->wirelessToolBar->addWidget(wireless_frame_);
373 #else
374 removeToolBar(main_ui_->wirelessToolBar);
375 main_ui_->menuView->removeAction(main_ui_->actionViewWirelessToolbar);
376 #endif
377
378 setWindowIcon(wsApp->normalIcon());
379 setTitlebarForCaptureFile();
380 setMenusForCaptureFile();
381 setForCapturedPackets(false);
382 setMenusForFileSet(false);
383 interfaceSelectionChanged();
384 loadWindowGeometry();
385
386 #ifndef HAVE_LUA
387 main_ui_->actionAnalyzeReloadLuaPlugins->setVisible(false);
388 #endif
389
390 qRegisterMetaType<FilterAction::Action>("FilterAction::Action");
391 qRegisterMetaType<FilterAction::ActionType>("FilterAction::ActionType");
392 connect(this, SIGNAL(filterAction(QString, FilterAction::Action, FilterAction::ActionType)),
393 this, SLOT(queuedFilterAction(QString, FilterAction::Action, FilterAction::ActionType)),
394 Qt::QueuedConnection);
395
396 //To prevent users use features before initialization complete
397 //Otherwise unexpected problems may occur
398 setFeaturesEnabled(false);
399 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(setFeaturesEnabled()));
400 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(applyGlobalCommandLineOptions()));
401 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(zoomText()));
402 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(initViewColorizeMenu()));
403 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addStatsPluginsToMenu()));
404 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addDynamicMenus()));
405 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(addPluginIFStructures()));
406 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(initConversationMenus()));
407 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(initExportObjectsMenus()));
408
409 connect(wsApp, SIGNAL(profileChanging()), this, SLOT(saveWindowGeometry()));
410 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutPanes()));
411 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(layoutToolbars()));
412 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(updatePreferenceActions()));
413 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(zoomText()));
414 connect(wsApp, SIGNAL(preferencesChanged()), this, SLOT(setTitlebarForCaptureFile()));
415
416 connect(wsApp, SIGNAL(updateRecentCaptureStatus(const QString &, qint64, bool)), this, SLOT(updateRecentCaptures()));
417 updateRecentCaptures();
418
419 #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN)
420 connect(wsApp, SIGNAL(softwareUpdateRequested()), this, SLOT(softwareUpdateRequested()),
421 Qt::BlockingQueuedConnection);
422 connect(wsApp, SIGNAL(softwareUpdateClose()), this, SLOT(close()),
423 Qt::BlockingQueuedConnection);
424 #endif
425
426 df_combo_box_ = new DisplayFilterCombo(this);
427
428 funnel_statistics_ = new FunnelStatistics(this, capture_file_);
429 connect(funnel_statistics_, &FunnelStatistics::setDisplayFilter, this, &MainWindow::setDisplayFilter);
430 connect(funnel_statistics_, SIGNAL(openCaptureFile(QString, QString)),
431 this, SLOT(openCaptureFile(QString, QString)));
432
433 file_set_dialog_ = new FileSetDialog(this);
434 connect(file_set_dialog_, SIGNAL(fileSetOpenCaptureFile(QString)),
435 this, SLOT(openCaptureFile(QString)));
436
437 initMainToolbarIcons();
438
439 main_ui_->displayFilterToolBar->insertWidget(main_ui_->actionNewDisplayFilterExpression, df_combo_box_);
440
441 // Make sure filter expressions overflow into a menu instead of a
442 // larger toolbar. We do this by adding them to a child toolbar.
443 // https://bugreports.qt.io/browse/QTBUG-2472
444 FilterExpressionToolBar *filter_expression_toolbar_ = new FilterExpressionToolBar(this);
445 connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterPreferences, this, &MainWindow::onFilterPreferences);
446 connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterSelected, this, &MainWindow::onFilterSelected);
447 connect(filter_expression_toolbar_, &FilterExpressionToolBar::filterEdit, this, &MainWindow::onFilterEdit);
448
449 main_ui_->displayFilterToolBar->addWidget(filter_expression_toolbar_);
450
451 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
452 connect(wireless_frame_, SIGNAL(showWirelessPreferences(QString)),
453 this, SLOT(showPreferencesDialog(QString)));
454 #endif
455
456 main_ui_->goToFrame->hide();
457 connect(main_ui_->goToFrame, SIGNAL(visibilityChanged(bool)),
458 main_ui_->actionGoGoToPacket, SLOT(setChecked(bool)));
459
460 // XXX For some reason the cursor is drawn funny with an input mask set
461 // https://bugreports.qt-project.org/browse/QTBUG-7174
462
463 main_ui_->searchFrame->hide();
464 connect(main_ui_->searchFrame, SIGNAL(visibilityChanged(bool)),
465 main_ui_->actionEditFindPacket, SLOT(setChecked(bool)));
466
467 main_ui_->addressEditorFrame->hide();
468 main_ui_->columnEditorFrame->hide();
469 main_ui_->preferenceEditorFrame->hide();
470 main_ui_->filterExpressionFrame->hide();
471
472 #ifndef HAVE_LIBPCAP
473 main_ui_->menuCapture->setEnabled(false);
474 #endif
475
476 // Set OS specific shortcuts for fullscreen mode
477 #if defined(Q_OS_MAC)
478 main_ui_->actionViewFullScreen->setShortcut(QKeySequence::FullScreen);
479 #else
480 main_ui_->actionViewFullScreen->setShortcut(QKeySequence(Qt::Key_F11));
481 #endif
482
483 #if defined(Q_OS_MAC)
484
485 main_ui_->goToPacketLabel->setAttribute(Qt::WA_MacSmallSize, true);
486 main_ui_->goToLineEdit->setAttribute(Qt::WA_MacSmallSize, true);
487 main_ui_->goToGo->setAttribute(Qt::WA_MacSmallSize, true);
488 main_ui_->goToCancel->setAttribute(Qt::WA_MacSmallSize, true);
489
490 main_ui_->actionEditPreferences->setMenuRole(QAction::PreferencesRole);
491
492 #endif // Q_OS_MAC
493
494 #ifdef HAVE_SOFTWARE_UPDATE
495 QAction *update_sep = main_ui_->menuHelp->insertSeparator(main_ui_->actionHelpAbout);
496 main_ui_->menuHelp->insertAction(update_sep, update_action_);
497 connect(update_action_, SIGNAL(triggered()), this, SLOT(checkForUpdates()));
498 #endif
499 master_split_.setObjectName("splitterMaster");
500 extra_split_.setObjectName("splitterExtra");
501 master_split_.setChildrenCollapsible(false);
502 extra_split_.setChildrenCollapsible(false);
503 main_ui_->mainStack->addWidget(&master_split_);
504
505 empty_pane_.setObjectName("emptyPane");
506 empty_pane_.setVisible(false);
507
508 packet_list_ = new PacketList(&master_split_);
509 main_ui_->wirelessTimelineWidget->setPacketList(packet_list_);
510 connect(packet_list_, SIGNAL(framesSelected(QList<int>)), this, SLOT(setMenusForSelectedPacket()));
511 connect(packet_list_, SIGNAL(framesSelected(QList<int>)), this, SIGNAL(framesSelected(QList<int>)));
512
513 connect(main_ui_->menuPacketComment, SIGNAL(aboutToShow()), this, SLOT(setEditCommentsMenu()));
514
515 proto_tree_ = new ProtoTree(&master_split_);
516 proto_tree_->installEventFilter(this);
517
518 packet_list_->setProtoTree(proto_tree_);
519 packet_list_->installEventFilter(this);
520
521 packet_diagram_ = new PacketDiagram(&master_split_);
522
523 welcome_page_ = main_ui_->welcomePage;
524
525 connect(proto_tree_, &ProtoTree::fieldSelected,
526 this, &MainWindow::fieldSelected);
527 connect(packet_list_, &PacketList::fieldSelected,
528 this, &MainWindow::fieldSelected);
529 connect(this, &MainWindow::fieldSelected,
530 this, &MainWindow::setMenusForSelectedTreeRow);
531 connect(this, &MainWindow::fieldSelected,
532 main_ui_->statusBar, &MainStatusBar::selectedFieldChanged);
533
534 connect(this, &MainWindow::fieldHighlight,
535 main_ui_->statusBar, &MainStatusBar::highlightedFieldChanged);
536 connect(wsApp, &WiresharkApplication::captureActive,
537 this, &MainWindow::captureActive);
538
539 byte_view_tab_ = new ByteViewTab(&master_split_);
540
541 // Packet list and proto tree must exist before these are called.
542 setMenusForSelectedPacket();
543 setMenusForSelectedTreeRow();
544
545 initShowHideMainWidgets();
546 initTimeDisplayFormatMenu();
547 initTimePrecisionFormatMenu();
548 initFreezeActions();
549 updatePreferenceActions();
550 updateRecentActions();
551 setForCaptureInProgress(false);
552
553 setTabOrder(df_combo_box_->lineEdit(), packet_list_);
554 setTabOrder(packet_list_, proto_tree_);
555
556 connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)),
557 this, SLOT(captureEventHandler(CaptureEvent)));
558 connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)),
559 wsApp, SLOT(captureEventHandler(CaptureEvent)));
560 connect(&capture_file_, SIGNAL(captureEvent(CaptureEvent)),
561 main_ui_->statusBar, SLOT(captureEventHandler(CaptureEvent)));
562
563 connect(wsApp, SIGNAL(columnsChanged()),
564 packet_list_, SLOT(columnsChanged()));
565 connect(wsApp, SIGNAL(preferencesChanged()),
566 packet_list_, SLOT(preferencesChanged()));
567 connect(wsApp, SIGNAL(recentPreferencesRead()),
568 this, SLOT(applyRecentPaneGeometry()));
569 connect(wsApp, SIGNAL(recentPreferencesRead()),
570 this, SLOT(updateRecentActions()));
571 connect(wsApp, SIGNAL(packetDissectionChanged()),
572 this, SLOT(redissectPackets()), Qt::QueuedConnection);
573
574 connect(wsApp, SIGNAL(checkDisplayFilter()),
575 this, SLOT(checkDisplayFilter()));
576 connect(wsApp, SIGNAL(fieldsChanged()),
577 this, SLOT(fieldsChanged()));
578 connect(wsApp, SIGNAL(reloadLuaPlugins()),
579 this, SLOT(reloadLuaPlugins()));
580
581 connect(main_ui_->mainStack, SIGNAL(currentChanged(int)),
582 this, SLOT(mainStackChanged(int)));
583
584 connect(welcome_page_, SIGNAL(startCapture()),
585 this, SLOT(startCapture()));
586 connect(welcome_page_, SIGNAL(recentFileActivated(QString)),
587 this, SLOT(openCaptureFile(QString)));
588
589 connect(main_ui_->addressEditorFrame, &AddressEditorFrame::redissectPackets,
590 this, &MainWindow::redissectPackets);
591 connect(main_ui_->addressEditorFrame, &AddressEditorFrame::showNameResolutionPreferences,
592 this, &MainWindow::showPreferencesDialog);
593 connect(main_ui_->preferenceEditorFrame, &PreferenceEditorFrame::showProtocolPreferences,
594 this, &MainWindow::showPreferencesDialog);
595 connect(main_ui_->filterExpressionFrame, &FilterExpressionFrame::showPreferencesDialog,
596 this, &MainWindow::showPreferencesDialog);
597 connect(main_ui_->filterExpressionFrame, &FilterExpressionFrame::filterExpressionsChanged,
598 filter_expression_toolbar_, &FilterExpressionToolBar::filterExpressionsChanged);
599
600 /* Connect change of capture file */
601 connect(this, &MainWindow::setCaptureFile,
602 main_ui_->searchFrame, &SearchFrame::setCaptureFile);
603 connect(this, &MainWindow::setCaptureFile,
604 main_ui_->statusBar, &MainStatusBar::setCaptureFile);
605 connect(this, &MainWindow::setCaptureFile,
606 packet_list_, &PacketList::setCaptureFile);
607 connect(this, &MainWindow::setCaptureFile,
608 proto_tree_, &ProtoTree::setCaptureFile);
609
610 connect(wsApp, SIGNAL(zoomMonospaceFont(QFont)),
611 packet_list_, SLOT(setMonospaceFont(QFont)));
612 connect(wsApp, SIGNAL(zoomMonospaceFont(QFont)),
613 proto_tree_, SLOT(setMonospaceFont(QFont)));
614
615 connect(main_ui_->actionGoNextPacket, SIGNAL(triggered()),
616 packet_list_, SLOT(goNextPacket()));
617 connect(main_ui_->actionGoPreviousPacket, SIGNAL(triggered()),
618 packet_list_, SLOT(goPreviousPacket()));
619 connect(main_ui_->actionGoFirstPacket, SIGNAL(triggered()),
620 packet_list_, SLOT(goFirstPacket()));
621 connect(main_ui_->actionGoLastPacket, SIGNAL(triggered()),
622 packet_list_, SLOT(goLastPacket()));
623 connect(main_ui_->actionGoNextHistoryPacket, SIGNAL(triggered()),
624 packet_list_, SLOT(goNextHistoryPacket()));
625 connect(main_ui_->actionGoPreviousHistoryPacket, SIGNAL(triggered()),
626 packet_list_, SLOT(goPreviousHistoryPacket()));
627
628 connect(main_ui_->actionViewExpandSubtrees, SIGNAL(triggered()),
629 proto_tree_, SLOT(expandSubtrees()));
630 connect(main_ui_->actionViewCollapseSubtrees, SIGNAL(triggered()),
631 proto_tree_, SLOT(collapseSubtrees()));
632 connect(main_ui_->actionViewExpandAll, SIGNAL(triggered()),
633 proto_tree_, SLOT(expandAll()));
634 connect(main_ui_->actionViewCollapseAll, SIGNAL(triggered()),
635 proto_tree_, SLOT(collapseAll()));
636
637 connect(packet_list_, SIGNAL(packetDissectionChanged()),
638 this, SLOT(redissectPackets()));
639 connect(packet_list_, SIGNAL(showColumnPreferences(QString)),
640 this, SLOT(showPreferencesDialog(QString)));
641 connect(packet_list_, SIGNAL(showProtocolPreferences(QString)),
642 this, SLOT(showPreferencesDialog(QString)));
643 connect(packet_list_, SIGNAL(editProtocolPreference(preference*, pref_module*)),
644 main_ui_->preferenceEditorFrame, SLOT(editPreference(preference*, pref_module*)));
645 connect(packet_list_, SIGNAL(editColumn(int)), this, SLOT(showColumnEditor(int)));
646 connect(main_ui_->columnEditorFrame, SIGNAL(columnEdited()),
647 packet_list_, SLOT(columnsChanged()));
648 connect(packet_list_, SIGNAL(doubleClicked(QModelIndex)),
649 this, SLOT(openPacketDialog()));
650 connect(packet_list_, SIGNAL(packetListScrolled(bool)),
651 main_ui_->actionGoAutoScroll, SLOT(setChecked(bool)));
652
653 connect(proto_tree_, SIGNAL(openPacketInNewWindow(bool)),
654 this, SLOT(openPacketDialog(bool)));
655 connect(proto_tree_, SIGNAL(showProtocolPreferences(QString)),
656 this, SLOT(showPreferencesDialog(QString)));
657 connect(proto_tree_, SIGNAL(editProtocolPreference(preference*, pref_module*)),
658 main_ui_->preferenceEditorFrame, SLOT(editPreference(preference*, pref_module*)));
659
660 connect(main_ui_->statusBar, SIGNAL(showExpertInfo()),
661 this, SLOT(on_actionAnalyzeExpertInfo_triggered()));
662
663 connect(main_ui_->statusBar, SIGNAL(stopLoading()),
664 &capture_file_, SLOT(stopLoading()));
665
666 connect(main_ui_->statusBar, SIGNAL(editCaptureComment()),
667 this, SLOT(on_actionStatisticsCaptureFileProperties_triggered()));
668
669 connect(main_ui_->menuApplyAsFilter, &QMenu::aboutToShow,
670 this, &MainWindow::filterMenuAboutToShow);
671 connect(main_ui_->menuPrepareAFilter, &QMenu::aboutToShow,
672 this, &MainWindow::filterMenuAboutToShow);
673
674 #ifdef HAVE_LIBPCAP
675 QTreeWidget *iface_tree = findChild<QTreeWidget *>("interfaceTree");
676 if (iface_tree) {
677 connect(iface_tree, SIGNAL(itemSelectionChanged()),
678 this, SLOT(interfaceSelectionChanged()));
679 }
680 connect(main_ui_->welcomePage, SIGNAL(captureFilterSyntaxChanged(bool)),
681 this, SLOT(captureFilterSyntaxChanged(bool)));
682
683 connect(this->welcome_page_, SIGNAL(showExtcapOptions(QString&)),
684 this, SLOT(showExtcapOptionsDialog(QString&)));
685
686 #endif // HAVE_LIBPCAP
687
688 /* Create plugin_if hooks */
689 plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_APPLY, plugin_if_mainwindow_apply_filter);
690 plugin_if_register_gui_cb(PLUGIN_IF_FILTER_ACTION_PREPARE, plugin_if_mainwindow_apply_filter);
691 plugin_if_register_gui_cb(PLUGIN_IF_PREFERENCE_SAVE, plugin_if_mainwindow_preference);
692 plugin_if_register_gui_cb(PLUGIN_IF_GOTO_FRAME, plugin_if_mainwindow_gotoframe);
693 #ifdef HAVE_LIBPCAP
694 plugin_if_register_gui_cb(PLUGIN_IF_GET_WS_INFO, plugin_if_mainwindow_get_ws_info);
695 #endif
696 plugin_if_register_gui_cb(PLUGIN_IF_GET_FRAME_DATA, plugin_if_mainwindow_get_frame_data);
697 plugin_if_register_gui_cb(PLUGIN_IF_GET_CAPTURE_FILE, plugin_if_mainwindow_get_capture_file);
698 plugin_if_register_gui_cb(PLUGIN_IF_REMOVE_TOOLBAR, plugin_if_mainwindow_update_toolbars);
699
700 /* Register Interface Toolbar callbacks */
701 iface_toolbar_register_cb(mainwindow_add_toolbar, mainwindow_remove_toolbar);
702
703 showWelcome();
704 }
705
~MainWindow()706 MainWindow::~MainWindow()
707 {
708 disconnect(main_ui_->mainStack, 0, 0, 0);
709
710 #ifndef Q_OS_MAC
711 // Below dialogs inherit GeometryStateDialog
712 // For reasons described in geometry_state_dialog.h no parent is set when
713 // instantiating the dialogs and as a resul objects are not automatically
714 // freed by its parent. Free then here explicitly to avoid leak and numerous
715 // Valgrind complaints.
716 delete file_set_dialog_;
717 delete capture_filter_dlg_;
718 delete display_filter_dlg_;
719 #ifdef HAVE_LIBPCAP
720 delete capture_options_dialog_;
721 #endif
722
723 #endif
724 delete main_ui_;
725 }
726
getFilter()727 QString MainWindow::getFilter()
728 {
729 return df_combo_box_->currentText();
730 }
731
createPopupMenu()732 QMenu *MainWindow::createPopupMenu()
733 {
734 QMenu *menu = new QMenu();
735 menu->addAction(main_ui_->actionViewMainToolbar);
736 menu->addAction(main_ui_->actionViewFilterToolbar);
737 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
738 menu->addAction(main_ui_->actionViewWirelessToolbar);
739 #endif
740
741 if (!main_ui_->menuInterfaceToolbars->actions().isEmpty()) {
742 QMenu *submenu = menu->addMenu(main_ui_->menuInterfaceToolbars->title());
743 foreach(QAction *action, main_ui_->menuInterfaceToolbars->actions()) {
744 submenu->addAction(action);
745 }
746 }
747
748 if (!main_ui_->menuAdditionalToolbars->actions().isEmpty()) {
749 QMenu *subMenu = menu->addMenu(main_ui_->menuAdditionalToolbars->title());
750 foreach(QAction *action, main_ui_->menuAdditionalToolbars->actions()) {
751 subMenu->addAction(action);
752 }
753 }
754
755 menu->addAction(main_ui_->actionViewStatusBar);
756
757 menu->addSeparator();
758 menu->addAction(main_ui_->actionViewPacketList);
759 menu->addAction(main_ui_->actionViewPacketDetails);
760 menu->addAction(main_ui_->actionViewPacketBytes);
761 menu->addAction(main_ui_->actionViewPacketDiagram);
762 return menu;
763 }
764
addInterfaceToolbar(const iface_toolbar * toolbar_entry)765 void MainWindow::addInterfaceToolbar(const iface_toolbar *toolbar_entry)
766 {
767 QMenu *menu = main_ui_->menuInterfaceToolbars;
768 bool visible = g_list_find_custom(recent.interface_toolbars, toolbar_entry->menu_title, (GCompareFunc)strcmp) ? true : false;
769
770 QString title = QString().fromUtf8(toolbar_entry->menu_title);
771 QAction *action = new QAction(title, menu);
772 action->setEnabled(true);
773 action->setCheckable(true);
774 action->setChecked(visible);
775 action->setToolTip(tr("Show or hide the toolbar"));
776
777 QAction *before = NULL;
778 foreach(QAction *action, menu->actions()) {
779 // Ensure we add the menu entries in sorted order
780 if (action->text().compare(title, Qt::CaseInsensitive) > 0) {
781 before = action;
782 break;
783 }
784 }
785 menu->insertAction(before, action);
786
787 InterfaceToolbar *interface_toolbar = new InterfaceToolbar(this, toolbar_entry);
788 connect(wsApp, SIGNAL(appInitialized()), interface_toolbar, SLOT(interfaceListChanged()));
789 connect(wsApp, SIGNAL(localInterfaceListChanged()), interface_toolbar, SLOT(interfaceListChanged()));
790
791 QToolBar *toolbar = new QToolBar(this);
792 toolbar->addWidget(interface_toolbar);
793 toolbar->setMovable(false);
794 toolbar->setVisible(visible);
795
796 action->setData(QVariant::fromValue(toolbar));
797
798 addToolBar(Qt::TopToolBarArea, toolbar);
799 insertToolBarBreak(toolbar);
800
801 if (show_hide_actions_) {
802 show_hide_actions_->addAction(action);
803 }
804
805 menu->menuAction()->setVisible(true);
806 }
807
removeInterfaceToolbar(const gchar * menu_title)808 void MainWindow::removeInterfaceToolbar(const gchar *menu_title)
809 {
810 QMenu *menu = main_ui_->menuInterfaceToolbars;
811 QAction *action = NULL;
812 QMap<QAction *, QWidget *>::iterator i;
813
814 QString title = QString().fromUtf8(menu_title);
815 foreach(action, menu->actions()) {
816 if (title.compare(action->text()) == 0) {
817 break;
818 }
819 }
820
821 if (action) {
822 if (show_hide_actions_) {
823 show_hide_actions_->removeAction(action);
824 }
825 menu->removeAction(action);
826
827 QToolBar *toolbar = action->data().value<QToolBar *>();
828 removeToolBar(toolbar);
829
830 delete action;
831 delete toolbar;
832 }
833
834 menu->menuAction()->setVisible(!menu->actions().isEmpty());
835 }
836
setPipeInputHandler(gint source,gpointer user_data,ws_process_id * child_process,pipe_input_cb_t input_cb)837 void MainWindow::setPipeInputHandler(gint source, gpointer user_data, ws_process_id *child_process, pipe_input_cb_t input_cb)
838 {
839 pipe_source_ = source;
840 pipe_child_process_ = child_process;
841 pipe_user_data_ = user_data;
842 pipe_input_cb_ = input_cb;
843
844 #ifdef _WIN32
845 /* Tricky to use pipes in win9x, as no concept of wait. NT can
846 do this but that doesn't cover all win32 platforms. GTK can do
847 this but doesn't seem to work over processes. Attempt to do
848 something similar here, start a timer and check for data on every
849 timeout. */
850 /*ws_log(NULL, LOG_LEVEL_DEBUG, "pipe_input_set_handler: new");*/
851
852 if (pipe_timer_) {
853 disconnect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
854 delete pipe_timer_;
855 }
856
857 pipe_timer_ = new QTimer(this);
858 connect(pipe_timer_, SIGNAL(timeout()), this, SLOT(pipeTimeout()));
859 connect(pipe_timer_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
860 pipe_timer_->start(200);
861 #else
862 if (pipe_notifier_) {
863 disconnect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
864 delete pipe_notifier_;
865 }
866
867 pipe_notifier_ = new QSocketNotifier(pipe_source_, QSocketNotifier::Read);
868 // XXX ui/gtk/gui_utils.c sets the encoding. Do we need to do the same?
869 connect(pipe_notifier_, SIGNAL(activated(int)), this, SLOT(pipeActivated(int)));
870 connect(pipe_notifier_, SIGNAL(destroyed()), this, SLOT(pipeNotifierDestroyed()));
871 #endif
872 }
873
eventFilter(QObject * obj,QEvent * event)874 bool MainWindow::eventFilter(QObject *obj, QEvent *event) {
875
876 // The user typed some text. Start filling in a filter.
877 // We may need to be more choosy here. We just need to catch events for the packet list,
878 // proto tree, and main welcome widgets.
879 if (event->type() == QEvent::KeyPress) {
880 QKeyEvent *kevt = static_cast<QKeyEvent *>(event);
881 if (kevt->text().length() > 0 && kevt->text()[0].isPrint() &&
882 !(kevt->modifiers() & Qt::ControlModifier)) {
883 df_combo_box_->lineEdit()->insert(kevt->text());
884 df_combo_box_->lineEdit()->setFocus();
885 return true;
886 }
887 }
888
889 return QMainWindow::eventFilter(obj, event);
890 }
891
event(QEvent * event)892 bool MainWindow::event(QEvent *event)
893 {
894 switch (event->type()) {
895 case QEvent::ApplicationPaletteChange:
896 initMainToolbarIcons();
897 break;
898 default:
899 break;
900
901 }
902 return QMainWindow::event(event);
903 }
904
keyPressEvent(QKeyEvent * event)905 void MainWindow::keyPressEvent(QKeyEvent *event) {
906
907 // Explicitly focus on the display filter combo.
908 if (event->modifiers() & Qt::ControlModifier && event->key() == Qt::Key_Slash) {
909 df_combo_box_->setFocus(Qt::ShortcutFocusReason);
910 return;
911 }
912
913 if (wsApp->focusWidget() == main_ui_->goToLineEdit) {
914 if (event->modifiers() == Qt::NoModifier) {
915 if (event->key() == Qt::Key_Escape) {
916 on_goToCancel_clicked();
917 } else if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
918 on_goToGo_clicked();
919 }
920 }
921 return; // goToLineEdit didn't want it and we don't either.
922 }
923
924 // Move up & down the packet list.
925 if (event->key() == Qt::Key_F7) {
926 packet_list_->goPreviousPacket();
927 } else if (event->key() == Qt::Key_F8) {
928 packet_list_->goNextPacket();
929 }
930
931 // Move along, citizen.
932 QMainWindow::keyPressEvent(event);
933 }
934
closeEvent(QCloseEvent * event)935 void MainWindow::closeEvent(QCloseEvent *event) {
936 saveWindowGeometry();
937
938 /* If we're in the middle of stopping a capture, don't do anything;
939 the user can try deleting the window after the capture stops. */
940 if (capture_stopping_) {
941 event->ignore();
942 return;
943 }
944
945 QString before_what(tr(" before quitting"));
946 if (!testCaptureFileClose(before_what, Quit)) {
947 event->ignore();
948 return;
949 }
950
951 #ifdef HAVE_LIBPCAP
952 if (capture_options_dialog_) capture_options_dialog_->close();
953 #endif
954 // Make sure we kill any open dumpcap processes.
955 delete welcome_page_;
956
957 // One of the many places we assume one main window.
958 if (!wsApp->isInitialized()) {
959 // If we're still initializing, QCoreApplication::quit() won't
960 // exit properly because we are not in the event loop. This
961 // means that the application won't clean up after itself. We
962 // might want to call wsApp->processEvents() during startup
963 // instead so that we can do a normal exit here.
964 exit(0);
965 }
966 wsApp->quit();
967 // When the main loop is not yet running (i.e. when openCaptureFile is
968 // executing in main.cpp), the above quit action has no effect.
969 // Schedule a quit action for the next execution of the main loop.
970 QMetaObject::invokeMethod(wsApp, "quit", Qt::QueuedConnection);
971 }
972
973 // XXX On windows the drag description is "Copy". It should be "Open" or
974 // "Merge" as appropriate. It looks like we need access to IDataObject in
975 // order to set DROPDESCRIPTION.
dragEnterEvent(QDragEnterEvent * event)976 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
977 {
978 if (!event->mimeData()->hasUrls())
979 {
980 event->ignore();
981 return;
982 }
983
984 if (!main_ui_->actionFileOpen->isEnabled()) {
985 // We could alternatively call setAcceptDrops(!capture_in_progress)
986 // in setMenusForCaptureInProgress but that wouldn't provide feedback.
987
988 wsApp->pushStatus(WiresharkApplication::TemporaryStatus, tr("Unable to drop files during capture."));
989 event->setDropAction(Qt::IgnoreAction);
990 event->ignore();
991 return;
992 }
993
994 bool have_files = false;
995 foreach(QUrl drag_url, event->mimeData()->urls()) {
996 if (!drag_url.toLocalFile().isEmpty()) {
997 have_files = true;
998 break;
999 }
1000 }
1001
1002 if (have_files) {
1003 event->acceptProposedAction();
1004 }
1005 }
1006
dropEvent(QDropEvent * event)1007 void MainWindow::dropEvent(QDropEvent *event)
1008 {
1009 if (!event->mimeData()->hasUrls())
1010 {
1011 event->ignore();
1012 return;
1013 }
1014
1015 QList<QByteArray> local_files;
1016 int max_dropped_files = 100; // Arbitrary
1017
1018 foreach(QUrl drop_url, event->mimeData()->urls()) {
1019 QString drop_file = drop_url.toLocalFile();
1020 if (!drop_file.isEmpty()) {
1021 local_files << drop_file.toUtf8();
1022 if (local_files.size() >= max_dropped_files) {
1023 break;
1024 }
1025 }
1026 }
1027
1028 event->acceptProposedAction();
1029
1030 if (local_files.size() < 1) {
1031 event->ignore();
1032 return;
1033 }
1034
1035 event->accept();
1036
1037 if (local_files.size() == 1) {
1038 openCaptureFile(local_files.at(0));
1039 return;
1040 }
1041
1042 const char **in_filenames = g_new(const char *, local_files.size());
1043 char *tmpname = NULL;
1044
1045 for (int i = 0; i < local_files.size(); i++) {
1046 in_filenames[i] = local_files.at(i).constData();
1047 }
1048
1049 /* merge the files in chronological order */
1050 if (cf_merge_files_to_tempfile(this, &tmpname, local_files.size(),
1051 in_filenames,
1052 wtap_pcapng_file_type_subtype(),
1053 FALSE) == CF_OK) {
1054 /* Merge succeeded; close the currently-open file and try
1055 to open the merged capture file. */
1056 openCaptureFile(tmpname, QString(), WTAP_TYPE_AUTO, TRUE);
1057 }
1058
1059 g_free(tmpname);
1060 g_free(in_filenames);
1061 }
1062
1063 // Apply recent settings to the main window geometry.
1064 // We haven't loaded the preferences at this point so we assume that the
1065 // position and size preference are enabled.
1066 // Note we might end up with unexpected screen geometries if the user
1067 // unplugs or plugs in a monitor:
1068 // https://bugreports.qt.io/browse/QTBUG-44213
loadWindowGeometry()1069 void MainWindow::loadWindowGeometry()
1070 {
1071 int min_sensible_dimension = 200;
1072
1073 #ifndef Q_OS_MAC
1074 if (recent.gui_geometry_main_maximized) {
1075 setWindowState(Qt::WindowMaximized);
1076 } else
1077 #endif
1078 {
1079 QRect recent_geom(recent.gui_geometry_main_x, recent.gui_geometry_main_y,
1080 recent.gui_geometry_main_width, recent.gui_geometry_main_height);
1081 if (!rect_on_screen(recent_geom)) {
1082 // We're not visible on any screens. See if we can move onscreen
1083 // without resizing.
1084 recent_geom.moveTo(50, 50); // recent.c defaults to 20.
1085 }
1086
1087 if (!rect_on_screen(recent_geom)) {
1088 // Give up and use the default geometry.
1089 return;
1090 }
1091
1092 // if (prefs.gui_geometry_save_position) {
1093 move(recent_geom.topLeft());
1094 // }
1095
1096 if (// prefs.gui_geometry_save_size &&
1097 recent_geom.width() > min_sensible_dimension &&
1098 recent_geom.height() > min_sensible_dimension) {
1099 resize(recent_geom.size());
1100 }
1101 }
1102 }
1103
saveWindowGeometry()1104 void MainWindow::saveWindowGeometry()
1105 {
1106 if (prefs.gui_geometry_save_position) {
1107 recent.gui_geometry_main_x = pos().x();
1108 recent.gui_geometry_main_y = pos().y();
1109 }
1110
1111 if (prefs.gui_geometry_save_size) {
1112 recent.gui_geometry_main_width = size().width();
1113 recent.gui_geometry_main_height = size().height();
1114 }
1115
1116 if (prefs.gui_geometry_save_maximized) {
1117 // On macOS this is false when it shouldn't be
1118 recent.gui_geometry_main_maximized = isMaximized();
1119 }
1120
1121 if (master_split_.sizes().length() > 0) {
1122 recent.gui_geometry_main_upper_pane = master_split_.sizes()[0];
1123 }
1124
1125 if (master_split_.sizes().length() > 2) {
1126 recent.gui_geometry_main_lower_pane = master_split_.sizes()[1];
1127 } else if (extra_split_.sizes().length() > 0) {
1128 recent.gui_geometry_main_lower_pane = extra_split_.sizes()[0];
1129 }
1130 }
1131
1132 // Our event loop becomes nested whenever we call update_progress_dlg, which
1133 // includes several places in file.c. The GTK+ UI stays out of trouble by
1134 // showing a modal progress dialog. We attempt to do the equivalent below by
1135 // disabling parts of the main window. At a minumum the ProgressFrame in the
1136 // main status bar must remain accessible.
1137 //
1138 // We might want to do this any time the main status bar progress frame is
1139 // shown and hidden.
freeze()1140 void MainWindow::freeze()
1141 {
1142 freeze_focus_ = wsApp->focusWidget();
1143
1144 // XXX Alternatively we could just disable and enable the main menu.
1145 for (int i = 0; i < freeze_actions_.size(); i++) {
1146 QAction *action = freeze_actions_[i].first;
1147 freeze_actions_[i].second = action->isEnabled();
1148 action->setEnabled(false);
1149 }
1150 main_ui_->centralWidget->setEnabled(false);
1151 }
1152
thaw()1153 void MainWindow::thaw()
1154 {
1155 main_ui_->centralWidget->setEnabled(true);
1156 for (int i = 0; i < freeze_actions_.size(); i++) {
1157 freeze_actions_[i].first->setEnabled(freeze_actions_[i].second);
1158 }
1159
1160 if (freeze_focus_) freeze_focus_->setFocus();
1161 freeze_focus_ = NULL;
1162 }
1163
mergeCaptureFile()1164 void MainWindow::mergeCaptureFile()
1165 {
1166 QString file_name = "";
1167 QString read_filter = "";
1168 dfilter_t *rfcode = NULL;
1169 int err;
1170
1171 if (!capture_file_.capFile())
1172 return;
1173
1174 if (prefs.gui_ask_unsaved) {
1175 if (cf_has_unsaved_data(capture_file_.capFile())) {
1176 QMessageBox msg_dialog;
1177 gchar *display_basename;
1178 int response;
1179
1180 msg_dialog.setIcon(QMessageBox::Question);
1181 /* This file has unsaved data; ask the user whether to save
1182 the capture. */
1183 if (capture_file_.capFile()->is_tempfile) {
1184 msg_dialog.setText(tr("Save packets before merging?"));
1185 msg_dialog.setInformativeText(tr("A temporary capture file can't be merged."));
1186 } else {
1187 /*
1188 * Format the message.
1189 */
1190 display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
1191 msg_dialog.setText(QString(tr("Save changes in \"%1\" before merging?")).arg(display_basename));
1192 g_free(display_basename);
1193 msg_dialog.setInformativeText(tr("Changes must be saved before the files can be merged."));
1194 }
1195
1196 msg_dialog.setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
1197 msg_dialog.setDefaultButton(QMessageBox::Save);
1198
1199 response = msg_dialog.exec();
1200
1201 switch (response) {
1202
1203 case QMessageBox::Save:
1204 /* Save the file but don't close it */
1205 saveCaptureFile(capture_file_.capFile(), false);
1206 break;
1207
1208 case QMessageBox::Cancel:
1209 default:
1210 /* Don't do the merge. */
1211 return;
1212 }
1213 }
1214 }
1215
1216 for (;;) {
1217 CaptureFileDialog merge_dlg(this, capture_file_.capFile(), read_filter);
1218 int file_type;
1219 cf_status_t merge_status;
1220 char *in_filenames[2];
1221 char *tmpname;
1222
1223 if (merge_dlg.merge(file_name)) {
1224 gchar *err_msg;
1225
1226 if (!dfilter_compile(qUtf8Printable(read_filter), &rfcode, &err_msg)) {
1227 /* Not valid. Tell the user, and go back and run the file
1228 selection box again once they dismiss the alert. */
1229 // Similar to commandline_info.jfilter section in main().
1230 QMessageBox::warning(this, tr("Invalid Read Filter"),
1231 QString(tr("The filter expression %1 isn't a valid read filter. (%2).").arg(read_filter, err_msg)),
1232 QMessageBox::Ok);
1233 g_free(err_msg);
1234 continue;
1235 }
1236 } else {
1237 return;
1238 }
1239
1240 file_type = capture_file_.capFile()->cd_t;
1241
1242 /* Try to merge or append the two files */
1243 if (merge_dlg.mergeType() == 0) {
1244 /* chronological order */
1245 in_filenames[0] = g_strdup(capture_file_.capFile()->filename);
1246 in_filenames[1] = qstring_strdup(file_name);
1247 merge_status = cf_merge_files_to_tempfile(this, &tmpname, 2, in_filenames, file_type, FALSE);
1248 } else if (merge_dlg.mergeType() <= 0) {
1249 /* prepend file */
1250 in_filenames[0] = qstring_strdup(file_name);
1251 in_filenames[1] = g_strdup(capture_file_.capFile()->filename);
1252 merge_status = cf_merge_files_to_tempfile(this, &tmpname, 2, in_filenames, file_type, TRUE);
1253 } else {
1254 /* append file */
1255 in_filenames[0] = g_strdup(capture_file_.capFile()->filename);
1256 in_filenames[1] = qstring_strdup(file_name);
1257 merge_status = cf_merge_files_to_tempfile(this, &tmpname, 2, in_filenames, file_type, TRUE);
1258 }
1259
1260 g_free(in_filenames[0]);
1261 g_free(in_filenames[1]);
1262
1263 if (merge_status != CF_OK) {
1264 dfilter_free(rfcode);
1265 g_free(tmpname);
1266 continue;
1267 }
1268
1269 cf_close(capture_file_.capFile());
1270
1271 /* Try to open the merged capture file. */
1272 CaptureFile::globalCapFile()->window = this;
1273 if (cf_open(CaptureFile::globalCapFile(), tmpname, WTAP_TYPE_AUTO, TRUE /* temporary file */, &err) != CF_OK) {
1274 /* We couldn't open it; fail. */
1275 CaptureFile::globalCapFile()->window = NULL;
1276 dfilter_free(rfcode);
1277 g_free(tmpname);
1278 return;
1279 }
1280
1281 /* Attach the new read filter to "cf" ("cf_open()" succeeded, so
1282 it closed the previous capture file, and thus destroyed any
1283 previous read filter attached to "cf"). */
1284 cf_set_rfcode(CaptureFile::globalCapFile(), rfcode);
1285
1286 switch (cf_read(CaptureFile::globalCapFile(), FALSE)) {
1287
1288 case CF_READ_OK:
1289 case CF_READ_ERROR:
1290 /* Just because we got an error, that doesn't mean we were unable
1291 to read any of the file; we handle what we could get from the
1292 file. */
1293 break;
1294
1295 case CF_READ_ABORTED:
1296 /* The user bailed out of re-reading the capture file; the
1297 capture file has been closed - just free the capture file name
1298 string and return (without changing the last containing
1299 directory). */
1300 g_free(tmpname);
1301 return;
1302 }
1303
1304 /* Save the name of the containing directory specified in the path name. */
1305 wsApp->setLastOpenDirFromFilename(tmpname);
1306 g_free(tmpname);
1307 main_ui_->statusBar->showExpert();
1308 return;
1309 }
1310
1311 }
1312
importCaptureFile()1313 void MainWindow::importCaptureFile() {
1314 ImportTextDialog import_dlg;
1315
1316 QString before_what(tr(" before importing a capture"));
1317 if (!testCaptureFileClose(before_what))
1318 return;
1319
1320 import_dlg.exec();
1321
1322 if (import_dlg.result() != QDialog::Accepted) {
1323 showWelcome();
1324 return;
1325 }
1326
1327 openCaptureFile(import_dlg.capfileName());
1328 }
1329
saveCaptureFile(capture_file * cf,bool dont_reopen)1330 bool MainWindow::saveCaptureFile(capture_file *cf, bool dont_reopen) {
1331 QString file_name;
1332 gboolean discard_comments;
1333
1334 if (cf->is_tempfile) {
1335 /* This is a temporary capture file, so saving it means saving
1336 it to a permanent file. Prompt the user for a location
1337 to which to save it. Don't require that the file format
1338 support comments - if it's a temporary capture file, it's
1339 probably pcapng, which supports comments and, if it's
1340 not pcapng, let the user decide what they want to do
1341 if they've added comments. */
1342 return saveAsCaptureFile(cf, FALSE, dont_reopen);
1343 } else {
1344 if (cf->unsaved_changes) {
1345 cf_write_status_t status;
1346
1347 /* This is not a temporary capture file, but it has unsaved
1348 changes, so saving it means doing a "safe save" on top
1349 of the existing file, in the same format - no UI needed
1350 unless the file has comments and the file's format doesn't
1351 support them.
1352
1353 If the file has comments, does the file's format support them?
1354 If not, ask the user whether they want to discard the comments
1355 or choose a different format. */
1356 switch (CaptureFileDialog::checkSaveAsWithComments(this, cf, cf->cd_t)) {
1357
1358 case SAVE:
1359 /* The file can be saved in the specified format as is;
1360 just drive on and save in the format they selected. */
1361 discard_comments = FALSE;
1362 break;
1363
1364 case SAVE_WITHOUT_COMMENTS:
1365 /* The file can't be saved in the specified format as is,
1366 but it can be saved without the comments, and the user
1367 said "OK, discard the comments", so save it in the
1368 format they specified without the comments. */
1369 discard_comments = TRUE;
1370 break;
1371
1372 case SAVE_IN_ANOTHER_FORMAT:
1373 /* There are file formats in which we can save this that
1374 support comments, and the user said not to delete the
1375 comments. Do a "Save As" so the user can select
1376 one of those formats and choose a file name. */
1377 return saveAsCaptureFile(cf, TRUE, dont_reopen);
1378
1379 case CANCELLED:
1380 /* The user said "forget it". Just return. */
1381 return false;
1382
1383 default:
1384 /* Squelch warnings that discard_comments is being used
1385 uninitialized. */
1386 ws_assert_not_reached();
1387 return false;
1388 }
1389
1390 /* XXX - cf->filename might get freed out from under us, because
1391 the code path through which cf_save_records() goes currently
1392 closes the current file and then opens and reloads the saved file,
1393 so make a copy and free it later. */
1394 file_name = cf->filename;
1395 status = cf_save_records(cf, qUtf8Printable(file_name), cf->cd_t, cf->compression_type,
1396 discard_comments, dont_reopen);
1397 switch (status) {
1398
1399 case CF_WRITE_OK:
1400 /* The save succeeded; we're done.
1401 If we discarded comments, redraw the packet list to reflect
1402 any packets that no longer have comments. */
1403 if (discard_comments)
1404 packet_list_queue_draw();
1405
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 *
1408 break;
1409
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? */
1415 break;
1416
1417 case CF_WRITE_ABORTED:
1418 /* The write was aborted; just drive on. */
1419 return false;
1420 }
1421 }
1422 /* Otherwise just do nothing. */
1423 }
1424
1425 return true;
1426 }
1427
saveAsCaptureFile(capture_file * cf,bool must_support_comments,bool dont_reopen)1428 bool MainWindow::saveAsCaptureFile(capture_file *cf, bool must_support_comments, bool dont_reopen) {
1429 QString file_name = "";
1430 int file_type;
1431 wtap_compression_type compression_type;
1432 cf_write_status_t status;
1433 gchar *dirname;
1434 gboolean discard_comments = FALSE;
1435
1436 if (!cf) {
1437 return false;
1438 }
1439
1440 for (;;) {
1441 CaptureFileDialog save_as_dlg(this, cf);
1442
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)) {
1447
1448 case SAVE:
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;
1452 break;
1453
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;
1460 break;
1461
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;
1470 continue;
1471
1472 case CANCELLED:
1473 /* The user said "forget it". Just get rid of the dialog box
1474 and return. */
1475 return false;
1476 }
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;
1481
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 Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
1485 msg_dialog.exec();
1486 return false;
1487 }
1488 compression_type = save_as_dlg.compressionType();
1489
1490 #ifdef Q_OS_WIN
1491 // the Windows dialog does not fixup extensions, do it manually here.
1492 fileAddExtension(file_name, file_type, compression_type);
1493 #endif // Q_OS_WIN
1494
1495 //#ifndef _WIN32
1496 // /* If the file exists and it's user-immutable or not writable,
1497 // ask the user whether they want to override that. */
1498 // if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) {
1499 // /* They don't. Let them try another file name or cancel. */
1500 // continue;
1501 // }
1502 //#endif
1503
1504 /* Attempt to save the file */
1505 status = cf_save_records(cf, qUtf8Printable(file_name), file_type, compression_type,
1506 discard_comments, dont_reopen);
1507 switch (status) {
1508
1509 case CF_WRITE_OK:
1510 /* The save succeeded; we're done. */
1511 /* Save the directory name for future file dialogs. */
1512 dirname = qstring_strdup(file_name); /* Overwrites cf_name */
1513 set_last_open_dir(get_dirname(dirname));
1514 g_free(dirname);
1515 /* If we discarded comments, redraw the packet list to reflect
1516 any packets that no longer have comments. */
1517 if (discard_comments)
1518 packet_list_queue_draw();
1519
1520 cf->unsaved_changes = false; //we just saved so we signal that we have no unsaved changes
1521 updateForUnsavedChanges(); // we update the title bar to remove the *
1522 /* Add this filename to the list of recent files in the "Recent Files" submenu */
1523 add_menu_recent_capture_file(qUtf8Printable(file_name));
1524 return true;
1525
1526 case CF_WRITE_ERROR:
1527 /* The save failed; let the user try again. */
1528 continue;
1529
1530 case CF_WRITE_ABORTED:
1531 /* The user aborted the save; just return. */
1532 return false;
1533 }
1534 }
1535 return true;
1536 }
1537
exportSelectedPackets()1538 void MainWindow::exportSelectedPackets() {
1539 QString file_name = "";
1540 int file_type;
1541 wtap_compression_type compression_type;
1542 packet_range_t range;
1543 cf_write_status_t status;
1544 gchar *dirname;
1545 bool discard_comments = false;
1546
1547 if (!capture_file_.capFile())
1548 return;
1549
1550 /* Init the packet range */
1551 packet_range_init(&range, capture_file_.capFile());
1552 range.process_filtered = TRUE;
1553 range.include_dependents = TRUE;
1554
1555 QList<int> rows = packet_list_->selectedRows(true);
1556
1557 QStringList entries;
1558 foreach (int row, rows)
1559 entries << QString::number(row);
1560 QString selRange = entries.join(",");
1561
1562 for (;;) {
1563 CaptureFileDialog esp_dlg(this, capture_file_.capFile());
1564
1565 /* If the file has comments, does the format the user selected
1566 support them? If not, ask the user whether they want to
1567 discard the comments or choose a different format. */
1568 switch (esp_dlg.exportSelectedPackets(file_name, &range, selRange)) {
1569
1570 case SAVE:
1571 /* The file can be saved in the specified format as is;
1572 just drive on and save in the format they selected. */
1573 discard_comments = FALSE;
1574 break;
1575
1576 case SAVE_WITHOUT_COMMENTS:
1577 /* The file can't be saved in the specified format as is,
1578 but it can be saved without the comments, and the user
1579 said "OK, discard the comments", so save it in the
1580 format they specified without the comments. */
1581 discard_comments = TRUE;
1582 break;
1583
1584 case SAVE_IN_ANOTHER_FORMAT:
1585 /* There are file formats in which we can save this that
1586 support comments, and the user said not to delete the
1587 comments. The combo box of file formats has had the
1588 formats that don't support comments trimmed from it,
1589 so run the dialog again, to let the user decide
1590 whether to save in one of those formats or give up. */
1591 continue;
1592
1593 case CANCELLED:
1594 /* The user said "forget it". Just get rid of the dialog box
1595 and return. */
1596 goto cleanup;
1597 }
1598
1599 /*
1600 * Check that we're not going to save on top of the current
1601 * capture file.
1602 * We do it here so we catch all cases ...
1603 * Unfortunately, the file requester gives us an absolute file
1604 * name and the read file name may be relative (if supplied on
1605 * the command line). From Joerg Mayer.
1606 */
1607 if (files_identical(capture_file_.capFile()->filename, qUtf8Printable(file_name))) {
1608 QMessageBox msg_box;
1609 gchar *display_basename = g_filename_display_basename(qUtf8Printable(file_name));
1610
1611 msg_box.setIcon(QMessageBox::Critical);
1612 msg_box.setText(QString(tr("Unable to export to \"%1\".").arg(display_basename)));
1613 msg_box.setInformativeText(tr("You cannot export packets to the current capture file."));
1614 msg_box.setStandardButtons(QMessageBox::Ok);
1615 msg_box.setDefaultButton(QMessageBox::Ok);
1616 msg_box.exec();
1617 g_free(display_basename);
1618 continue;
1619 }
1620
1621 file_type = esp_dlg.selectedFileType();
1622 if (file_type == WTAP_FILE_TYPE_SUBTYPE_UNKNOWN) {
1623 /* This "should not happen". */
1624 QMessageBox msg_box;
1625
1626 msg_box.setIcon(QMessageBox::Critical);
1627 msg_box.setText(tr("Unknown file type returned by export dialog."));
1628 msg_box.setInformativeText(tr("Please report this as a Wireshark issue at https://gitlab.com/wireshark/wireshark/-/issues."));
1629 msg_box.exec();
1630 goto cleanup;
1631 }
1632 compression_type = esp_dlg.compressionType();
1633 #ifdef Q_OS_WIN
1634 // the Windows dialog does not fixup extensions, do it manually here.
1635 fileAddExtension(file_name, file_type, compression_type);
1636 #endif // Q_OS_WIN
1637
1638 //#ifndef _WIN32
1639 // /* If the file exists and it's user-immutable or not writable,
1640 // ask the user whether they want to override that. */
1641 // if (!file_target_unwritable_ui(top_level, qUtf8Printable(file_name))) {
1642 // /* They don't. Let them try another file name or cancel. */
1643 // continue;
1644 // }
1645 //#endif
1646
1647 /* Attempt to save the file */
1648 status = cf_export_specified_packets(capture_file_.capFile(), qUtf8Printable(file_name), &range, file_type, compression_type);
1649 switch (status) {
1650
1651 case CF_WRITE_OK:
1652 /* The save succeeded; we're done. */
1653 /* Save the directory name for future file dialogs. */
1654 dirname = qstring_strdup(file_name); /* Overwrites cf_name */
1655 set_last_open_dir(get_dirname(dirname));
1656 g_free(dirname);
1657 /* If we discarded comments, redraw the packet list to reflect
1658 any packets that no longer have comments. */
1659 if (discard_comments)
1660 packet_list_queue_draw();
1661 /* Add this filename to the list of recent files in the "Recent Files" submenu */
1662 add_menu_recent_capture_file(qUtf8Printable(file_name));
1663 goto cleanup;
1664
1665 case CF_WRITE_ERROR:
1666 /* The save failed; let the user try again. */
1667 continue;
1668
1669 case CF_WRITE_ABORTED:
1670 /* The user aborted the save; just return. */
1671 goto cleanup;
1672 }
1673 }
1674
1675 cleanup:
1676 packet_range_cleanup(&range);
1677 }
1678
exportDissections(export_type_e export_type)1679 void MainWindow::exportDissections(export_type_e export_type) {
1680 capture_file *cf = capture_file_.capFile();
1681 g_return_if_fail(cf);
1682
1683 QList<int> rows = packet_list_->selectedRows(true);
1684
1685 QStringList entries;
1686 foreach (int row, rows)
1687 entries << QString::number(row);
1688 QString selRange = entries.join(",");
1689
1690 ExportDissectionDialog *ed_dlg = new ExportDissectionDialog(this, cf, export_type, selRange);
1691 ed_dlg->setWindowModality(Qt::ApplicationModal);
1692 ed_dlg->setAttribute(Qt::WA_DeleteOnClose);
1693 ed_dlg->show();
1694 }
1695
1696 #ifdef Q_OS_WIN
1697 /*
1698 * Ensure that:
1699 *
1700 * If the file is to be compressed:
1701 *
1702 * if there is a set of extensions used by the file type to be used,
1703 * the file name has one of those extensions followed by the extension
1704 * for the compression type to be used;
1705 *
1706 * otherwise, the file name has the extension for the compression type
1707 * to be used;
1708 *
1709 * otherwise:
1710 *
1711 * if there is a set of extensions used by the file type to be used,
1712 * the file name has one of those extensions.
1713 */
fileAddExtension(QString & file_name,int file_type,wtap_compression_type compression_type)1714 void MainWindow::fileAddExtension(QString &file_name, int file_type, wtap_compression_type compression_type) {
1715 QString file_name_lower;
1716 GSList *extensions_list;
1717 const char *compressed_file_extension;
1718 gboolean add_extension_for_file_type;
1719
1720 /* Lower-case the file name, so the extension matching is case-insensitive. */
1721 file_name_lower = file_name.toLower();
1722
1723 /* Get a list of all extensions used for this file type; don't
1724 include the ones with compression type extensions, as we
1725 only want to check for the extension for the compression
1726 type we'll be using. */
1727 extensions_list = wtap_get_file_extensions_list(file_type, FALSE);
1728
1729 /* Get the extension for the compression type we'll be using;
1730 NULL is returned if the type isn't supported or compression
1731 is not being done. */
1732 compressed_file_extension = wtap_compression_type_extension(compression_type);
1733
1734 if (extensions_list != NULL) {
1735 GSList *extension;
1736
1737 /* This file type has one or more extensions.
1738 Start out assuming we need to add the default one. */
1739 add_extension_for_file_type = TRUE;
1740
1741 /* OK, see if the file has one of those extensions, followed
1742 by the appropriate compression type extension if it's to be
1743 compressed. */
1744 for (extension = extensions_list; extension != NULL;
1745 extension = g_slist_next(extension)) {
1746 QString file_suffix = QString(".") + (char *)extension->data;
1747 if (compressed_file_extension != NULL)
1748 file_suffix += QString(".") + compressed_file_extension;
1749 if (file_name_lower.endsWith(file_suffix)) {
1750 /*
1751 * The file name has one of the extensions for this file
1752 * type, followed by a compression type extension if
1753 * appropriate, so we don't need to add an extension for
1754 * the file type or the compression type.
1755 */
1756 add_extension_for_file_type = FALSE;
1757 break;
1758 }
1759 }
1760 } else {
1761 /* We have no extensions for this file type. Just check
1762 to see if we need to add an extension for the compressed
1763 file type.
1764
1765 Start out assuming we do. */
1766 add_extension_for_file_type = TRUE;
1767 if (compressed_file_extension != NULL) {
1768 QString file_suffix = QString(".") + compressed_file_extension;
1769 if (file_name_lower.endsWith(file_suffix)) {
1770 /*
1771 * The file name has the appropriate compressed file extension,
1772 * so we don't need to add an extension for the compression
1773 * type.
1774 */
1775 add_extension_for_file_type = FALSE;
1776 }
1777 }
1778 }
1779
1780 /*
1781 * If we need to add an extension for the file type or compressed
1782 * file type, do so.
1783 */
1784 if (add_extension_for_file_type) {
1785 if (wtap_default_file_extension(file_type) != NULL) {
1786 /* This file type has a default extension; append it. */
1787 file_name += QString(".") + wtap_default_file_extension(file_type);
1788 }
1789 if (compression_type != WTAP_UNCOMPRESSED) {
1790 /*
1791 * The file is to be compressed, so append the extension for
1792 * its compression type.
1793 */
1794 file_name += QString(".") + compressed_file_extension;
1795 }
1796 }
1797 }
1798 #endif // Q_OS_WIN
1799
testCaptureFileClose(QString before_what,FileCloseContext context)1800 bool MainWindow::testCaptureFileClose(QString before_what, FileCloseContext context) {
1801 bool capture_in_progress = false;
1802 bool do_close_file = false;
1803
1804 if (!capture_file_.capFile() || capture_file_.capFile()->state == FILE_CLOSED)
1805 return true; /* Already closed, nothing to do */
1806
1807 if (capture_file_.capFile()->read_lock) {
1808 /*
1809 * If the file is being redissected, we cannot stop the capture since
1810 * that would crash and burn "cf_read", so stop early. Ideally all
1811 * callers should be modified to check this condition and act
1812 * accordingly (ignore action or queue it up), so print a warning.
1813 */
1814 ws_warning("Refusing to close \"%s\" which is being read.", capture_file_.capFile()->filename);
1815 return false;
1816 }
1817
1818 #ifdef HAVE_LIBPCAP
1819 if (capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1820 /*
1821 * This (FILE_READ_IN_PROGRESS) is true if we're reading a capture file
1822 * *or* if we're doing a live capture. From the capture file itself we
1823 * cannot differentiate the cases, so check the current capture session.
1824 */
1825 capture_in_progress = captureSession()->state != CAPTURE_STOPPED;
1826 }
1827 #endif
1828
1829 if (prefs.gui_ask_unsaved) {
1830 if (cf_has_unsaved_data(capture_file_.capFile())) {
1831 QMessageBox msg_dialog;
1832 QString question;
1833 QString infotext;
1834 QPushButton *save_button;
1835 QPushButton *discard_button;
1836
1837 msg_dialog.setIcon(QMessageBox::Question);
1838 msg_dialog.setWindowTitle("Unsaved packets" UTF8_HORIZONTAL_ELLIPSIS);
1839
1840 /* This file has unsaved data or there's a capture in
1841 progress; ask the user whether to save the data. */
1842 if (capture_in_progress && context != Restart) {
1843 question = tr("Do you want to stop the capture and save the captured packets%1?").arg(before_what);
1844 infotext = tr("Your captured packets will be lost if you don't save them.");
1845 } else if (capture_file_.capFile()->is_tempfile) {
1846 if (context == Reload) {
1847 // Reloading a tempfile will keep the packets, so this is not unsaved packets
1848 question = tr("Do you want to save the changes you've made%1?").arg(before_what);
1849 infotext = tr("Your changes will be lost if you don't save them.");
1850 } else {
1851 question = tr("Do you want to save the captured packets%1?").arg(before_what);
1852 infotext = tr("Your captured packets will be lost if you don't save them.");
1853 }
1854 } else {
1855 // No capture in progress and not a tempfile, so this is not unsaved packets
1856 gchar *display_basename = g_filename_display_basename(capture_file_.capFile()->filename);
1857 question = tr("Do you want to save the changes you've made to the capture file \"%1\"%2?").arg(display_basename, before_what);
1858 infotext = tr("Your changes will be lost if you don't save them.");
1859 g_free(display_basename);
1860 }
1861
1862 msg_dialog.setText(question);
1863 msg_dialog.setInformativeText(infotext);
1864
1865 // XXX Text comes from ui/gtk/stock_icons.[ch]
1866 // Note that the button roles differ from the GTK+ version.
1867 // Cancel = RejectRole
1868 // Save = AcceptRole
1869 // Don't Save = DestructiveRole
1870 msg_dialog.addButton(QMessageBox::Cancel);
1871
1872 if (capture_in_progress) {
1873 QString save_button_text;
1874 if (context == Restart) {
1875 save_button_text = tr("Save before Continue");
1876 } else {
1877 save_button_text = tr("Stop and Save");
1878 }
1879 save_button = msg_dialog.addButton(save_button_text, QMessageBox::AcceptRole);
1880 } else {
1881 save_button = msg_dialog.addButton(QMessageBox::Save);
1882 }
1883 msg_dialog.setDefaultButton(save_button);
1884
1885 QString discard_button_text;
1886 if (capture_in_progress) {
1887 switch (context) {
1888 case Quit:
1889 discard_button_text = tr("Stop and Quit &without Saving");
1890 break;
1891 case Restart:
1892 discard_button_text = tr("Continue &without Saving");
1893 break;
1894 default:
1895 discard_button_text = tr("Stop and Continue &without Saving");
1896 break;
1897 }
1898 } else {
1899 switch (context) {
1900 case Quit:
1901 discard_button_text = tr("Quit &without Saving");
1902 break;
1903 case Restart:
1904 default:
1905 discard_button_text = tr("Continue &without Saving");
1906 break;
1907 }
1908 }
1909 discard_button = msg_dialog.addButton(discard_button_text, QMessageBox::DestructiveRole);
1910
1911 #if defined(Q_OS_MAC)
1912 /*
1913 * In macOS, the "default button" is not necessarily the
1914 * button that has the input focus; Enter/Return activates
1915 * the default button, and the spacebar activates the button
1916 * that has the input focus, and they might be different
1917 * buttons.
1918 *
1919 * In a "do you want to save" dialog, for example, the
1920 * "save" button is the default button, and the "don't
1921 * save" button has the input focus, so you can press
1922 * Enter/Return to save or space not to save (or Escape
1923 * to dismiss the dialog).
1924 *
1925 * In Qt terms, this means "no auto-default", as auto-default
1926 * makes the button with the input focus the default button,
1927 * so that Enter/Return will activate it.
1928 */
1929 QList<QAbstractButton *> buttons = msg_dialog.buttons();
1930 for (int i = 0; i < buttons.size(); ++i) {
1931 QPushButton *button = static_cast<QPushButton *>(buttons.at(i));;
1932 button->setAutoDefault(false);
1933 }
1934
1935 /*
1936 * It also means that the "don't save" button should be the one
1937 * initially given the focus.
1938 */
1939 discard_button->setFocus();
1940 #endif
1941
1942 msg_dialog.exec();
1943 /* According to the Qt doc:
1944 * when using QMessageBox with custom buttons, exec() function returns an opaque value.
1945 *
1946 * Therefore we should use clickedButton() to determine which button was clicked. */
1947
1948 if (msg_dialog.clickedButton() == save_button) {
1949 #ifdef HAVE_LIBPCAP
1950 /* If there's a capture in progress, we have to stop the capture
1951 and then do the save. */
1952 if (capture_in_progress)
1953 captureStop();
1954 #endif
1955 /* Save the file and close it */
1956 // XXX if no packets were captured, any unsaved comments set by
1957 // the user are silently discarded because capFile() is null.
1958 if (capture_file_.capFile() && saveCaptureFile(capture_file_.capFile(), true) == false)
1959 return false;
1960 do_close_file = true;
1961 } else if (msg_dialog.clickedButton() == discard_button) {
1962 /* Just close the file, discarding changes */
1963 do_close_file = true;
1964 } else {
1965 // cancelButton or some other unspecified button
1966 return false;
1967 }
1968 } else {
1969 /* Unchanged file or capturing with no packets */
1970 do_close_file = true;
1971 }
1972 } else {
1973 /* User asked not to be bothered by those prompts, just close it.
1974 XXX - should that apply only to saving temporary files? */
1975 do_close_file = true;
1976 }
1977
1978 /*
1979 * Are we done with this file and should we close the file?
1980 */
1981 if (do_close_file) {
1982 #ifdef HAVE_LIBPCAP
1983 /* If there's a capture in progress, we have to stop the capture
1984 and then do the close. */
1985 if (capture_in_progress)
1986 captureStop();
1987 else if (capture_file_.capFile() && capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
1988 /*
1989 * When an offline capture is being read, mark it as aborted.
1990 * cf_read will be responsible for actually closing the capture.
1991 *
1992 * We cannot just invoke cf_close here since cf_read is up in the
1993 * call chain. (update_progress_dlg can end up processing the Quit
1994 * event from the user which then ends up here.)
1995 * See also the above "read_lock" check.
1996 */
1997 capture_file_.capFile()->state = FILE_READ_ABORTED;
1998 return true;
1999 }
2000 #endif
2001 /* Clear MainWindow file name details */
2002 gbl_cur_main_window_->setMwFileName("");
2003
2004 /* captureStop() will close the file if not having any packets */
2005 if (capture_file_.capFile() && context != Restart && context != Reload)
2006 // Don't really close if Restart or Reload
2007 cf_close(capture_file_.capFile());
2008 }
2009
2010 return true; /* File closed */
2011 }
2012
captureStop()2013 void MainWindow::captureStop() {
2014 stopCapture();
2015
2016 while (capture_file_.capFile() && capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
2017 WiresharkApplication::processEvents();
2018 }
2019 }
2020
findTextCodecs()2021 void MainWindow::findTextCodecs() {
2022 const QList<int> mibs = QTextCodec::availableMibs();
2023 QRegularExpression ibmRegExp("^IBM([0-9]+).*$");
2024 QRegularExpression iso8859RegExp("^ISO-8859-([0-9]+).*$");
2025 QRegularExpression windowsRegExp("^WINDOWS-([0-9]+).*$");
2026 QRegularExpressionMatch match;
2027 for (int mib : mibs) {
2028 QTextCodec *codec = QTextCodec::codecForMib(mib);
2029 QString key = codec->name().toUpper();
2030 char rank;
2031
2032 if (key.localeAwareCompare("IBM") < 0) {
2033 rank = 1;
2034 } else if ((match = ibmRegExp.match(key)).hasMatch()) {
2035 rank = match.capturedRef(1).size(); // Up to 5
2036 } else if (key.localeAwareCompare("ISO-8859-") < 0) {
2037 rank = 6;
2038 } else if ((match = iso8859RegExp.match(key)).hasMatch()) {
2039 rank = 6 + match.capturedRef(1).size(); // Up to 6 + 2
2040 } else if (key.localeAwareCompare("WINDOWS-") < 0) {
2041 rank = 9;
2042 } else if ((match = windowsRegExp.match(key)).hasMatch()) {
2043 rank = 9 + match.capturedRef(1).size(); // Up to 9 + 4
2044 } else {
2045 rank = 14;
2046 }
2047 // This doesn't perfectly well order the IBM codecs because it's
2048 // annoying to properly place IBM00858 and IBM00924 in the middle of
2049 // code page numbers not zero padded to 5 digits.
2050 // We could manipulate the key further to have more commonly used
2051 // charsets earlier. IANA MIB ordering would be unxpected:
2052 // https://www.iana.org/assignments/character-sets/character-sets.xml
2053 // For data about use in HTTP (other protocols can be quite different):
2054 // https://w3techs.com/technologies/overview/character_encoding
2055
2056 key.prepend('0' + rank);
2057 // We use a map here because, due to backwards compatibility,
2058 // the same QTextCodec may be returned for multiple MIBs, which
2059 // happens for GBK/GB2312, EUC-KR/windows-949/UHC, and others.
2060 text_codec_map_.insert(key, codec);
2061 }
2062 }
2063
initMainToolbarIcons()2064 void MainWindow::initMainToolbarIcons()
2065 {
2066 // Normally 16 px. Reflects current GTK+ behavior and other Windows apps.
2067 int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize);
2068 #if !defined(Q_OS_WIN)
2069 // Force icons to 24x24 for now, otherwise actionFileOpen looks wonky.
2070 // The macOS HIG specifies 32-pixel icons but they're a little too
2071 // large IMHO.
2072 icon_size = icon_size * 3 / 2;
2073 #endif
2074 main_ui_->mainToolBar->setIconSize(QSize(icon_size, icon_size));
2075
2076 // Toolbar actions. The GNOME HIG says that we should have a menu icon for each
2077 // toolbar item but that clutters up our menu. Set menu icons sparingly.
2078
2079 main_ui_->actionCaptureStart->setIcon(StockIcon("x-capture-start"));
2080 main_ui_->actionCaptureStop->setIcon(StockIcon("x-capture-stop"));
2081 main_ui_->actionCaptureRestart->setIcon(StockIcon("x-capture-restart"));
2082 main_ui_->actionCaptureOptions->setIcon(StockIcon("x-capture-options"));
2083
2084 // Menu icons are disabled in main_window.ui for these items.
2085 main_ui_->actionFileOpen->setIcon(StockIcon("document-open"));
2086 main_ui_->actionFileSave->setIcon(StockIcon("x-capture-file-save"));
2087 main_ui_->actionFileClose->setIcon(StockIcon("x-capture-file-close"));
2088 main_ui_->actionViewReload->setIcon(StockIcon("x-capture-file-reload"));
2089
2090 main_ui_->actionEditFindPacket->setIcon(StockIcon("edit-find"));
2091 main_ui_->actionGoPreviousPacket->setIcon(StockIcon("go-previous"));
2092 main_ui_->actionGoNextPacket->setIcon(StockIcon("go-next"));
2093 main_ui_->actionGoGoToPacket->setIcon(StockIcon("go-jump"));
2094 main_ui_->actionGoFirstPacket->setIcon(StockIcon("go-first"));
2095 main_ui_->actionGoLastPacket->setIcon(StockIcon("go-last"));
2096 main_ui_->actionGoPreviousConversationPacket->setIcon(StockIcon("go-previous"));
2097 main_ui_->actionGoNextConversationPacket->setIcon(StockIcon("go-next"));
2098 #if defined(Q_OS_MAC)
2099 main_ui_->actionGoPreviousConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Comma));
2100 main_ui_->actionGoNextConversationPacket->setShortcut(QKeySequence(Qt::META | Qt::Key_Period));
2101 #endif
2102 main_ui_->actionGoPreviousHistoryPacket->setIcon(StockIcon("go-previous"));
2103 main_ui_->actionGoNextHistoryPacket->setIcon(StockIcon("go-next"));
2104 main_ui_->actionGoAutoScroll->setIcon(StockIcon("x-stay-last"));
2105
2106 main_ui_->actionViewColorizePacketList->setIcon(StockIcon("x-colorize-packets"));
2107
2108 QList<QKeySequence> zi_seq = main_ui_->actionViewZoomIn->shortcuts();
2109 zi_seq << QKeySequence(Qt::CTRL + Qt::Key_Equal);
2110 main_ui_->actionViewZoomIn->setIcon(StockIcon("zoom-in"));
2111 main_ui_->actionViewZoomIn->setShortcuts(zi_seq);
2112 main_ui_->actionViewZoomOut->setIcon(StockIcon("zoom-out"));
2113 main_ui_->actionViewNormalSize->setIcon(StockIcon("zoom-original"));
2114 main_ui_->actionViewResizeColumns->setIcon(StockIcon("x-resize-columns"));
2115
2116 main_ui_->actionNewDisplayFilterExpression->setIcon(StockIcon("list-add"));
2117 }
2118
initShowHideMainWidgets()2119 void MainWindow::initShowHideMainWidgets()
2120 {
2121 if (show_hide_actions_) {
2122 return;
2123 }
2124
2125 show_hide_actions_ = new QActionGroup(this);
2126 QMap<QAction *, QWidget *> shmw_actions;
2127
2128 show_hide_actions_->setExclusive(false);
2129 shmw_actions[main_ui_->actionViewMainToolbar] = main_ui_->mainToolBar;
2130 shmw_actions[main_ui_->actionViewFilterToolbar] = main_ui_->displayFilterToolBar;
2131 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
2132 shmw_actions[main_ui_->actionViewWirelessToolbar] = main_ui_->wirelessToolBar;
2133 #endif
2134 shmw_actions[main_ui_->actionViewStatusBar] = main_ui_->statusBar;
2135 shmw_actions[main_ui_->actionViewPacketList] = packet_list_;
2136 shmw_actions[main_ui_->actionViewPacketDetails] = proto_tree_;
2137 shmw_actions[main_ui_->actionViewPacketBytes] = byte_view_tab_;
2138 shmw_actions[main_ui_->actionViewPacketDiagram] = packet_diagram_;
2139
2140 foreach(QAction *shmwa, shmw_actions.keys()) {
2141 shmwa->setData(QVariant::fromValue(shmw_actions[shmwa]));
2142 show_hide_actions_->addAction(shmwa);
2143 }
2144
2145 // Initial hide the Interface Toolbar submenu
2146 main_ui_->menuInterfaceToolbars->menuAction()->setVisible(false);
2147
2148 /* Initially hide the additional toolbars menus */
2149 main_ui_->menuAdditionalToolbars->menuAction()->setVisible(false);
2150
2151 connect(show_hide_actions_, SIGNAL(triggered(QAction*)), this, SLOT(showHideMainWidgets(QAction*)));
2152 }
2153
initTimeDisplayFormatMenu()2154 void MainWindow::initTimeDisplayFormatMenu()
2155 {
2156 if (time_display_actions_) {
2157 return;
2158 }
2159
2160 time_display_actions_ = new QActionGroup(this);
2161
2162 td_actions[main_ui_->actionViewTimeDisplayFormatDateYMDandTimeOfDay] = TS_ABSOLUTE_WITH_YMD;
2163 td_actions[main_ui_->actionViewTimeDisplayFormatDateYDOYandTimeOfDay] = TS_ABSOLUTE_WITH_YDOY;
2164 td_actions[main_ui_->actionViewTimeDisplayFormatTimeOfDay] = TS_ABSOLUTE;
2165 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceEpoch] = TS_EPOCH;
2166 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSinceBeginningOfCapture] = TS_RELATIVE;
2167 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousCapturedPacket] = TS_DELTA;
2168 td_actions[main_ui_->actionViewTimeDisplayFormatSecondsSincePreviousDisplayedPacket] = TS_DELTA_DIS;
2169 td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYMDandTimeOfDay] = TS_UTC_WITH_YMD;
2170 td_actions[main_ui_->actionViewTimeDisplayFormatUTCDateYDOYandTimeOfDay] = TS_UTC_WITH_YDOY;
2171 td_actions[main_ui_->actionViewTimeDisplayFormatUTCTimeOfDay] = TS_UTC;
2172
2173 foreach(QAction* tda, td_actions.keys()) {
2174 tda->setData(QVariant::fromValue(td_actions[tda]));
2175 time_display_actions_->addAction(tda);
2176 }
2177
2178 connect(time_display_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampFormat(QAction*)));
2179 }
2180
initTimePrecisionFormatMenu()2181 void MainWindow::initTimePrecisionFormatMenu()
2182 {
2183 if (time_precision_actions_) {
2184 return;
2185 }
2186
2187 time_precision_actions_ = new QActionGroup(this);
2188
2189 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionAutomatic] = TS_PREC_AUTO;
2190 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionSeconds] = TS_PREC_FIXED_SEC;
2191 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionDeciseconds] = TS_PREC_FIXED_DSEC;
2192 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionCentiseconds] = TS_PREC_FIXED_CSEC;
2193 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMilliseconds] = TS_PREC_FIXED_MSEC;
2194 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionMicroseconds] = TS_PREC_FIXED_USEC;
2195 tp_actions[main_ui_->actionViewTimeDisplayFormatPrecisionNanoseconds] = TS_PREC_FIXED_NSEC;
2196
2197 foreach(QAction* tpa, tp_actions.keys()) {
2198 tpa->setData(QVariant::fromValue(tp_actions[tpa]));
2199 time_precision_actions_->addAction(tpa);
2200 }
2201
2202 connect(time_precision_actions_, SIGNAL(triggered(QAction*)), this, SLOT(setTimestampPrecision(QAction*)));
2203 }
2204
2205 // Menu items which will be disabled when we freeze() and whose state will
2206 // be restored when we thaw(). Add to the list as needed.
initFreezeActions()2207 void MainWindow::initFreezeActions()
2208 {
2209 QList<QAction *> freeze_actions = QList<QAction *>()
2210 << main_ui_->actionFileClose
2211 << main_ui_->actionViewReload
2212 << main_ui_->actionEditMarkPacket
2213 << main_ui_->actionEditMarkAllDisplayed
2214 << main_ui_->actionEditUnmarkAllDisplayed
2215 << main_ui_->actionEditIgnorePacket
2216 << main_ui_->actionEditIgnoreAllDisplayed
2217 << main_ui_->actionEditUnignoreAllDisplayed
2218 << main_ui_->actionEditSetTimeReference
2219 << main_ui_->actionEditUnsetAllTimeReferences;
2220
2221 foreach(QAction *action, freeze_actions) {
2222 freeze_actions_ << QPair<QAction *, bool>(action, false);
2223 }
2224 }
2225
initConversationMenus()2226 void MainWindow::initConversationMenus()
2227 {
2228 int i;
2229
2230 QList<QAction *> cc_actions = QList<QAction *>()
2231 << main_ui_->actionViewColorizeConversation1 << main_ui_->actionViewColorizeConversation2
2232 << main_ui_->actionViewColorizeConversation3 << main_ui_->actionViewColorizeConversation4
2233 << main_ui_->actionViewColorizeConversation5 << main_ui_->actionViewColorizeConversation6
2234 << main_ui_->actionViewColorizeConversation7 << main_ui_->actionViewColorizeConversation8
2235 << main_ui_->actionViewColorizeConversation9 << main_ui_->actionViewColorizeConversation10;
2236
2237 for (GList *conv_filter_list_entry = conv_filter_list; conv_filter_list_entry; conv_filter_list_entry = gxx_list_next(conv_filter_list_entry)) {
2238 // Main menu items
2239 conversation_filter_t* conv_filter = gxx_list_data(conversation_filter_t *, conv_filter_list_entry);
2240 ConversationAction *conv_action = new ConversationAction(main_ui_->menuConversationFilter, conv_filter);
2241 main_ui_->menuConversationFilter->addAction(conv_action);
2242
2243 connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*)));
2244 connect(conv_action, SIGNAL(triggered()), this, SLOT(applyConversationFilter()));
2245
2246 // Packet list context menu items
2247 packet_list_->conversationMenu()->addAction(conv_action);
2248
2249 QMenu *submenu = packet_list_->colorizeMenu()->addMenu(conv_action->text());
2250 i = 1;
2251
2252 foreach(QAction *cc_action, cc_actions) {
2253 conv_action = new ConversationAction(submenu, conv_filter);
2254 conv_action->setText(cc_action->text());
2255 conv_action->setIcon(cc_action->icon());
2256 conv_action->setColorNumber(i++);
2257 submenu->addAction(conv_action);
2258 connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*)));
2259 connect(conv_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered()));
2260 }
2261
2262 conv_action = new ConversationAction(submenu, conv_filter);
2263 conv_action->setText(main_ui_->actionViewColorizeNewColoringRule->text());
2264 submenu->addAction(conv_action);
2265 connect(this, SIGNAL(packetInfoChanged(_packet_info*)), conv_action, SLOT(setPacketInfo(_packet_info*)));
2266 connect(conv_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered()));
2267
2268 // Proto tree conversation menu is filled in in ProtoTree::contextMenuEvent.
2269 // We should probably do that here.
2270 }
2271
2272 // Proto tree colorization items
2273 i = 1;
2274 ColorizeAction *colorize_action;
2275 foreach(QAction *cc_action, cc_actions) {
2276 colorize_action = new ColorizeAction(proto_tree_->colorizeMenu());
2277 colorize_action->setText(cc_action->text());
2278 colorize_action->setIcon(cc_action->icon());
2279 colorize_action->setColorNumber(i++);
2280 proto_tree_->colorizeMenu()->addAction(colorize_action);
2281 connect(this, SIGNAL(fieldFilterChanged(QByteArray)), colorize_action, SLOT(setFieldFilter(QByteArray)));
2282 connect(colorize_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered()));
2283 }
2284
2285 colorize_action = new ColorizeAction(proto_tree_->colorizeMenu());
2286 colorize_action->setText(main_ui_->actionViewColorizeNewColoringRule->text());
2287 proto_tree_->colorizeMenu()->addAction(colorize_action);
2288 connect(this, SIGNAL(fieldFilterChanged(QByteArray)), colorize_action, SLOT(setFieldFilter(QByteArray)));
2289 connect(colorize_action, SIGNAL(triggered()), this, SLOT(colorizeActionTriggered()));
2290 }
2291
addExportObjectsMenuItem(const void *,void * value,void * userdata)2292 gboolean MainWindow::addExportObjectsMenuItem(const void *, void *value, void *userdata)
2293 {
2294 register_eo_t *eo = (register_eo_t*)value;
2295 MainWindow *window = (MainWindow*)userdata;
2296
2297 ExportObjectAction *export_action = new ExportObjectAction(window->main_ui_->menuFileExportObjects, eo);
2298 window->main_ui_->menuFileExportObjects->addAction(export_action);
2299
2300 //initially disable until a file is loaded (then file signals will take over)
2301 export_action->setEnabled(false);
2302
2303 connect(&window->capture_file_, SIGNAL(captureEvent(CaptureEvent)), export_action, SLOT(captureFileEvent(CaptureEvent)));
2304 connect(export_action, SIGNAL(triggered()), window, SLOT(applyExportObject()));
2305 return FALSE;
2306 }
2307
initExportObjectsMenus()2308 void MainWindow::initExportObjectsMenus()
2309 {
2310 eo_iterate_tables(addExportObjectsMenuItem, this);
2311 }
2312
2313 // Titlebar
setTitlebarForCaptureFile()2314 void MainWindow::setTitlebarForCaptureFile()
2315 {
2316 if (capture_file_.capFile() && capture_file_.capFile()->filename) {
2317 setWSWindowTitle(QString("[*]%1").arg(capture_file_.fileDisplayName()));
2318 //
2319 // XXX - on non-Mac platforms, put in the application
2320 // name? Or do so only for temporary files?
2321 //
2322 if (!capture_file_.capFile()->is_tempfile) {
2323 //
2324 // Set the file path; that way, for macOS, it'll set the
2325 // "proxy icon".
2326 //
2327 setWindowFilePath(capture_file_.filePath());
2328 }
2329 setWindowModified(cf_has_unsaved_data(capture_file_.capFile()));
2330 } else {
2331 /* We have no capture file. */
2332 setWSWindowTitle();
2333 }
2334 }
2335
replaceWindowTitleVariables(QString title)2336 QString MainWindow::replaceWindowTitleVariables(QString title)
2337 {
2338 title.replace("%P", get_profile_name());
2339 title.replace("%V", get_ws_vcs_version_info());
2340
2341 if (title.contains("%F")) {
2342 // %F is file path of the capture file.
2343 if (capture_file_.capFile()) {
2344 // get_dirname() will overwrite the argument so make a copy first
2345 char *filename = g_strdup(capture_file_.capFile()->filename);
2346 QString file(get_dirname(filename));
2347 g_free(filename);
2348 #ifndef _WIN32
2349 // Substitute HOME with ~
2350 QString homedir(g_getenv("HOME"));
2351 if (!homedir.isEmpty()) {
2352 homedir.remove(QRegExp("[/]+$"));
2353 file.replace(homedir, "~");
2354 }
2355 #endif
2356 title.replace("%F", file);
2357 } else {
2358 // No file loaded, no folder name
2359 title.remove("%F");
2360 }
2361 }
2362
2363 if (title.contains("%S")) {
2364 // %S is a conditional separator (" - ") that only shows when surrounded by variables
2365 // with values or static text. Remove repeating, leading and trailing separators.
2366 title.replace(QRegExp("(%S)+"), "%S");
2367 title.remove(QRegExp("^%S|%S$"));
2368 #ifdef __APPLE__
2369 // On macOS we separate with a unicode em dash
2370 title.replace("%S", " " UTF8_EM_DASH " ");
2371 #else
2372 title.replace("%S", " - ");
2373 #endif
2374 }
2375
2376 return title;
2377 }
2378
setWSWindowTitle(QString title)2379 void MainWindow::setWSWindowTitle(QString title)
2380 {
2381 if (title.isEmpty()) {
2382 title = tr("The Wireshark Network Analyzer");
2383 }
2384
2385 if (prefs.gui_prepend_window_title && prefs.gui_prepend_window_title[0]) {
2386 QString custom_title = replaceWindowTitleVariables(prefs.gui_prepend_window_title);
2387 if (custom_title.length() > 0) {
2388 title.prepend(QString("[%1] ").arg(custom_title));
2389 }
2390 }
2391
2392 if (prefs.gui_window_title && prefs.gui_window_title[0]) {
2393 QString custom_title = replaceWindowTitleVariables(prefs.gui_window_title);
2394 if (custom_title.length() > 0) {
2395 #ifdef __APPLE__
2396 // On macOS we separate the titles with a unicode em dash
2397 title.append(QString(" %1 %2").arg(UTF8_EM_DASH).arg(custom_title));
2398 #else
2399 title.append(QString(" [%1]").arg(custom_title));
2400 #endif
2401 }
2402 }
2403
2404 setWindowTitle(title);
2405 setWindowFilePath(NULL);
2406 }
2407
setTitlebarForCaptureInProgress()2408 void MainWindow::setTitlebarForCaptureInProgress()
2409 {
2410 if (capture_file_.capFile()) {
2411 setWSWindowTitle(tr("Capturing from %1").arg(cf_get_tempfile_source(capture_file_.capFile())));
2412 } else {
2413 /* We have no capture in progress. */
2414 setWSWindowTitle();
2415 }
2416 }
2417
2418 // Menu state
2419
2420 /* Enable or disable menu items based on whether you have a capture file
2421 you've finished reading and, if you have one, whether it's been saved
2422 and whether it could be saved except by copying the raw packet data. */
setMenusForCaptureFile(bool force_disable)2423 void MainWindow::setMenusForCaptureFile(bool force_disable)
2424 {
2425 bool enable = true;
2426 bool can_write = false;
2427 bool can_save = false;
2428 bool can_save_as = false;
2429
2430 if (force_disable || capture_file_.capFile() == NULL || capture_file_.capFile()->state == FILE_READ_IN_PROGRESS) {
2431 /* We have no capture file or we're currently reading a file */
2432 enable = false;
2433 } else {
2434 /* We have a capture file. Can we write or save? */
2435 can_write = cf_can_write_with_wiretap(capture_file_.capFile());
2436 can_save = cf_can_save(capture_file_.capFile());
2437 can_save_as = cf_can_save_as(capture_file_.capFile());
2438 }
2439
2440 main_ui_->actionViewReload_as_File_Format_or_Capture->setEnabled(enable);
2441 main_ui_->actionFileMerge->setEnabled(can_write);
2442 main_ui_->actionFileClose->setEnabled(enable);
2443 main_ui_->actionFileSave->setEnabled(can_save);
2444 main_ui_->actionFileSaveAs->setEnabled(can_save_as);
2445 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(enable);
2446 /*
2447 * "Export Specified Packets..." should be available only if
2448 * we can write the file out in at least one format.
2449 */
2450 main_ui_->actionFileExportPackets->setEnabled(can_write);
2451
2452 main_ui_->actionFileExportAsCArrays->setEnabled(enable);
2453 main_ui_->actionFileExportAsCSV->setEnabled(enable);
2454 main_ui_->actionFileExportAsPDML->setEnabled(enable);
2455 main_ui_->actionFileExportAsPlainText->setEnabled(enable);
2456 main_ui_->actionFileExportAsPSML->setEnabled(enable);
2457 main_ui_->actionFileExportAsJSON->setEnabled(enable);
2458
2459 main_ui_->actionFileExportPacketBytes->setEnabled(enable);
2460 main_ui_->actionFileExportPDU->setEnabled(enable);
2461 main_ui_->actionFileExportTLSSessionKeys->setEnabled(enable);
2462
2463 foreach(QAction *eo_action, main_ui_->menuFileExportObjects->actions()) {
2464 eo_action->setEnabled(enable);
2465 }
2466
2467 main_ui_->actionViewReload->setEnabled(enable);
2468
2469 #ifdef HAVE_SOFTWARE_UPDATE
2470 // We might want to enable or disable automatic checks here as well.
2471 update_action_->setEnabled(!can_save);
2472 #endif
2473 }
2474
setMenusForCaptureInProgress(bool capture_in_progress)2475 void MainWindow::setMenusForCaptureInProgress(bool capture_in_progress) {
2476 /* Either a capture was started or stopped; in either case, it's not
2477 in the process of stopping, so allow quitting. */
2478
2479 main_ui_->actionFileOpen->setEnabled(!capture_in_progress);
2480 main_ui_->menuOpenRecentCaptureFile->setEnabled(!capture_in_progress);
2481
2482 main_ui_->actionFileExportAsCArrays->setEnabled(capture_in_progress);
2483 main_ui_->actionFileExportAsCSV->setEnabled(capture_in_progress);
2484 main_ui_->actionFileExportAsPDML->setEnabled(capture_in_progress);
2485 main_ui_->actionFileExportAsPlainText->setEnabled(capture_in_progress);
2486 main_ui_->actionFileExportAsPSML->setEnabled(capture_in_progress);
2487 main_ui_->actionFileExportAsJSON->setEnabled(capture_in_progress);
2488
2489 main_ui_->actionFileExportPacketBytes->setEnabled(capture_in_progress);
2490 main_ui_->actionFileExportPDU->setEnabled(!capture_in_progress);
2491 main_ui_->actionFileExportTLSSessionKeys->setEnabled(capture_in_progress);
2492
2493 foreach(QAction *eo_action, main_ui_->menuFileExportObjects->actions()) {
2494 eo_action->setEnabled(capture_in_progress);
2495 }
2496
2497 main_ui_->menuFileSet->setEnabled(!capture_in_progress);
2498 main_ui_->actionFileQuit->setEnabled(true);
2499 #ifdef HAVE_SOFTWARE_UPDATE
2500 // We might want to enable or disable automatic checks here as well.
2501 update_action_->setEnabled(!capture_in_progress);
2502 #endif
2503
2504 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(capture_in_progress);
2505
2506 // XXX Fix packet list heading menu sensitivity
2507 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortAscending",
2508 // !capture_in_progress);
2509 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/SortDescending",
2510 // !capture_in_progress);
2511 // set_menu_sensitivity(ui_manager_packet_list_heading, "/PacketListHeadingPopup/NoSorting",
2512 // !capture_in_progress);
2513
2514 #ifdef HAVE_LIBPCAP
2515 main_ui_->actionCaptureOptions->setEnabled(!capture_in_progress);
2516 main_ui_->actionCaptureStart->setEnabled(!capture_in_progress);
2517 main_ui_->actionCaptureStart->setChecked(capture_in_progress);
2518 main_ui_->actionCaptureStop->setEnabled(capture_in_progress);
2519 main_ui_->actionCaptureRestart->setEnabled(capture_in_progress);
2520 main_ui_->actionCaptureRefreshInterfaces->setEnabled(!capture_in_progress);
2521 #endif /* HAVE_LIBPCAP */
2522
2523 }
2524
setMenusForCaptureStopping()2525 void MainWindow::setMenusForCaptureStopping() {
2526 main_ui_->actionFileQuit->setEnabled(false);
2527 #ifdef HAVE_SOFTWARE_UPDATE
2528 update_action_->setEnabled(false);
2529 #endif
2530 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(false);
2531 #ifdef HAVE_LIBPCAP
2532 main_ui_->actionCaptureStart->setChecked(false);
2533 main_ui_->actionCaptureStop->setEnabled(false);
2534 main_ui_->actionCaptureRestart->setEnabled(false);
2535 #endif /* HAVE_LIBPCAP */
2536 }
2537
setForCapturedPackets(bool have_captured_packets)2538 void MainWindow::setForCapturedPackets(bool have_captured_packets)
2539 {
2540 main_ui_->actionFilePrint->setEnabled(have_captured_packets);
2541
2542 // set_menu_sensitivity(ui_manager_packet_list_menu, "/PacketListMenuPopup/Print",
2543 // have_captured_packets);
2544
2545 main_ui_->actionEditFindPacket->setEnabled(have_captured_packets);
2546 main_ui_->actionEditFindNext->setEnabled(have_captured_packets);
2547 main_ui_->actionEditFindPrevious->setEnabled(have_captured_packets);
2548
2549 main_ui_->actionGoGoToPacket->setEnabled(have_captured_packets);
2550 main_ui_->actionGoPreviousPacket->setEnabled(have_captured_packets);
2551 main_ui_->actionGoNextPacket->setEnabled(have_captured_packets);
2552 main_ui_->actionGoFirstPacket->setEnabled(have_captured_packets);
2553 main_ui_->actionGoLastPacket->setEnabled(have_captured_packets);
2554 main_ui_->actionGoNextConversationPacket->setEnabled(have_captured_packets);
2555 main_ui_->actionGoPreviousConversationPacket->setEnabled(have_captured_packets);
2556
2557 main_ui_->actionViewZoomIn->setEnabled(have_captured_packets);
2558 main_ui_->actionViewZoomOut->setEnabled(have_captured_packets);
2559 main_ui_->actionViewNormalSize->setEnabled(have_captured_packets);
2560 main_ui_->actionViewResizeColumns->setEnabled(have_captured_packets);
2561
2562 main_ui_->actionStatisticsCaptureFileProperties->setEnabled(have_captured_packets);
2563 main_ui_->actionStatisticsProtocolHierarchy->setEnabled(have_captured_packets);
2564 main_ui_->actionStatisticsIOGraph->setEnabled(have_captured_packets);
2565 }
2566
setMenusForFileSet(bool enable_list_files)2567 void MainWindow::setMenusForFileSet(bool enable_list_files) {
2568 bool enable_next = fileset_get_next() != NULL && enable_list_files;
2569 bool enable_prev = fileset_get_previous() != NULL && enable_list_files;
2570
2571 main_ui_->actionFileSetListFiles->setEnabled(enable_list_files);
2572 main_ui_->actionFileSetNextFile->setEnabled(enable_next);
2573 main_ui_->actionFileSetPreviousFile->setEnabled(enable_prev);
2574 }
2575
setWindowIcon(const QIcon & icon)2576 void MainWindow::setWindowIcon(const QIcon &icon) {
2577 wsApp->setWindowIcon(icon);
2578 QMainWindow::setWindowIcon(icon);
2579 }
2580
updateForUnsavedChanges()2581 void MainWindow::updateForUnsavedChanges() {
2582 setTitlebarForCaptureFile();
2583 setMenusForCaptureFile();
2584 }
2585
changeEvent(QEvent * event)2586 void MainWindow::changeEvent(QEvent* event)
2587 {
2588 if (0 != event)
2589 {
2590 switch (event->type())
2591 {
2592 case QEvent::LanguageChange:
2593 main_ui_->retranslateUi(this);
2594 // make sure that the "Clear Menu" item is retranslated
2595 wsApp->emitAppSignal(WiresharkApplication::RecentCapturesChanged);
2596 break;
2597 case QEvent::LocaleChange: {
2598 QString locale = QLocale::system().name();
2599 locale.truncate(locale.lastIndexOf('_'));
2600 wsApp->loadLanguage(locale);
2601 }
2602 break;
2603 case QEvent::WindowStateChange:
2604 main_ui_->actionViewFullScreen->setChecked(this->isFullScreen());
2605 break;
2606 default:
2607 break;
2608 }
2609 }
2610 QMainWindow::changeEvent(event);
2611 }
2612
2613 /* Update main window items based on whether there's a capture in progress. */
setForCaptureInProgress(bool capture_in_progress,bool handle_toolbars,GArray * ifaces)2614 void MainWindow::setForCaptureInProgress(bool capture_in_progress, bool handle_toolbars, GArray *ifaces)
2615 {
2616 setMenusForCaptureInProgress(capture_in_progress);
2617
2618 #if defined(HAVE_LIBNL) && defined(HAVE_NL80211)
2619 wireless_frame_->setCaptureInProgress(capture_in_progress);
2620 #endif
2621
2622 #ifdef HAVE_LIBPCAP
2623 packet_list_->setCaptureInProgress(capture_in_progress);
2624 packet_list_->setVerticalAutoScroll(capture_in_progress && main_ui_->actionGoAutoScroll->isChecked());
2625
2626 // set_capture_if_dialog_for_capture_in_progress(capture_in_progress);
2627 #endif
2628
2629 if (handle_toolbars) {
2630 QList<InterfaceToolbar *> toolbars = findChildren<InterfaceToolbar *>();
2631 foreach(InterfaceToolbar *toolbar, toolbars) {
2632 if (capture_in_progress) {
2633 toolbar->startCapture(ifaces);
2634 } else {
2635 toolbar->stopCapture();
2636 }
2637 }
2638 }
2639 }
2640
2641 static QList<register_stat_group_t> menu_groups = QList<register_stat_group_t>()
2642 << REGISTER_ANALYZE_GROUP_UNSORTED
2643 << REGISTER_ANALYZE_GROUP_CONVERSATION_FILTER
2644 << REGISTER_STAT_GROUP_UNSORTED
2645 << REGISTER_STAT_GROUP_GENERIC
2646 << REGISTER_STAT_GROUP_CONVERSATION_LIST
2647 << REGISTER_STAT_GROUP_ENDPOINT_LIST
2648 << REGISTER_STAT_GROUP_RESPONSE_TIME
2649 << REGISTER_STAT_GROUP_RSERPOOL
2650 << REGISTER_STAT_GROUP_TELEPHONY
2651 << REGISTER_STAT_GROUP_TELEPHONY_ANSI
2652 << REGISTER_STAT_GROUP_TELEPHONY_GSM
2653 << REGISTER_STAT_GROUP_TELEPHONY_LTE
2654 << REGISTER_STAT_GROUP_TELEPHONY_MTP3
2655 << REGISTER_STAT_GROUP_TELEPHONY_SCTP
2656 << REGISTER_TOOLS_GROUP_UNSORTED;
2657
addMenuActions(QList<QAction * > & actions,int menu_group)2658 void MainWindow::addMenuActions(QList<QAction *> &actions, int menu_group)
2659 {
2660 foreach(QAction *action, actions) {
2661 switch (menu_group) {
2662 case REGISTER_ANALYZE_GROUP_UNSORTED:
2663 case REGISTER_STAT_GROUP_UNSORTED:
2664 main_ui_->menuStatistics->insertAction(
2665 main_ui_->actionStatistics_REGISTER_STAT_GROUP_UNSORTED,
2666 action);
2667 break;
2668 case REGISTER_STAT_GROUP_RESPONSE_TIME:
2669 main_ui_->menuServiceResponseTime->addAction(action);
2670 break;
2671 case REGISTER_STAT_GROUP_RSERPOOL:
2672 main_ui_->menuRSerPool->addAction(action);
2673 break;
2674 case REGISTER_STAT_GROUP_TELEPHONY:
2675 main_ui_->menuTelephony->addAction(action);
2676 break;
2677 case REGISTER_STAT_GROUP_TELEPHONY_ANSI:
2678 main_ui_->menuANSI->addAction(action);
2679 break;
2680 case REGISTER_STAT_GROUP_TELEPHONY_GSM:
2681 main_ui_->menuGSM->addAction(action);
2682 break;
2683 case REGISTER_STAT_GROUP_TELEPHONY_LTE:
2684 main_ui_->menuLTE->addAction(action);
2685 break;
2686 case REGISTER_STAT_GROUP_TELEPHONY_MTP3:
2687 main_ui_->menuMTP3->addAction(action);
2688 break;
2689 case REGISTER_TOOLS_GROUP_UNSORTED:
2690 {
2691 // Allow the creation of submenus. Mimics the behavor of
2692 // ui/gtk/main_menubar.c:add_menu_item_to_main_menubar
2693 // and GtkUIManager.
2694 //
2695 // For now we limit the insanity to the "Tools" menu.
2696 QStringList menu_path = action->text().split('/');
2697 QMenu *cur_menu = main_ui_->menuTools;
2698 while (menu_path.length() > 1) {
2699 QString menu_title = menu_path.takeFirst();
2700 QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
2701 if (!submenu) {
2702 submenu = cur_menu->addMenu(menu_title);
2703 submenu->setObjectName(menu_title.toLower());
2704 }
2705 cur_menu = submenu;
2706 }
2707 action->setText(menu_path.last());
2708 cur_menu->addAction(action);
2709 break;
2710 }
2711 default:
2712 // qDebug() << "FIX: Add" << action->text() << "to the menu";
2713 break;
2714 }
2715
2716 // Connect each action type to its corresponding slot. We to
2717 // distinguish various types of actions. Setting their objectName
2718 // seems to work OK.
2719 if (action->objectName() == TapParameterDialog::actionName()) {
2720 connect(action, SIGNAL(triggered(bool)), this, SLOT(openTapParameterDialog()));
2721 } else if (action->objectName() == FunnelStatistics::actionName()) {
2722 connect(action, SIGNAL(triggered(bool)), funnel_statistics_, SLOT(funnelActionTriggered()));
2723 }
2724 }
2725 }
removeMenuActions(QList<QAction * > & actions,int menu_group)2726 void MainWindow::removeMenuActions(QList<QAction *> &actions, int menu_group)
2727 {
2728 foreach(QAction *action, actions) {
2729 switch (menu_group) {
2730 case REGISTER_ANALYZE_GROUP_UNSORTED:
2731 case REGISTER_STAT_GROUP_UNSORTED:
2732 main_ui_->menuStatistics->removeAction(action);
2733 break;
2734 case REGISTER_STAT_GROUP_RESPONSE_TIME:
2735 main_ui_->menuServiceResponseTime->removeAction(action);
2736 break;
2737 case REGISTER_STAT_GROUP_RSERPOOL:
2738 main_ui_->menuRSerPool->removeAction(action);
2739 break;
2740 case REGISTER_STAT_GROUP_TELEPHONY:
2741 main_ui_->menuTelephony->removeAction(action);
2742 break;
2743 case REGISTER_STAT_GROUP_TELEPHONY_ANSI:
2744 main_ui_->menuANSI->removeAction(action);
2745 break;
2746 case REGISTER_STAT_GROUP_TELEPHONY_GSM:
2747 main_ui_->menuGSM->removeAction(action);
2748 break;
2749 case REGISTER_STAT_GROUP_TELEPHONY_LTE:
2750 main_ui_->menuLTE->removeAction(action);
2751 break;
2752 case REGISTER_STAT_GROUP_TELEPHONY_MTP3:
2753 main_ui_->menuMTP3->removeAction(action);
2754 break;
2755 case REGISTER_TOOLS_GROUP_UNSORTED:
2756 {
2757 // Allow removal of submenus.
2758 // For now we limit the insanity to the "Tools" menu.
2759 QStringList menu_path = action->text().split('/');
2760 QMenu *cur_menu = main_ui_->menuTools;
2761 while (menu_path.length() > 1) {
2762 QString menu_title = menu_path.takeFirst();
2763 QMenu *submenu = cur_menu->findChild<QMenu *>(menu_title.toLower(), Qt::FindDirectChildrenOnly);
2764 cur_menu = submenu;
2765 }
2766 cur_menu->removeAction(action);
2767 break;
2768 }
2769 default:
2770 // qDebug() << "FIX: Remove" << action->text() << "from the menu";
2771 break;
2772 }
2773 }
2774 }
2775
addDynamicMenus()2776 void MainWindow::addDynamicMenus()
2777 {
2778 // Manual additions
2779 wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_GSM, main_ui_->actionTelephonyGsmMapSummary);
2780 wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteMacStatistics);
2781 wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteRlcStatistics);
2782 wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_LTE, main_ui_->actionTelephonyLteRlcGraph);
2783 wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY_MTP3, main_ui_->actionTelephonyMtp3Summary);
2784 wsApp->addDynamicMenuGroupItem(REGISTER_STAT_GROUP_TELEPHONY, main_ui_->actionTelephonySipFlows);
2785
2786 // Fill in each menu
2787 foreach(register_stat_group_t menu_group, menu_groups) {
2788 QList<QAction *>actions = wsApp->dynamicMenuGroupItems(menu_group);
2789 addMenuActions(actions, menu_group);
2790 }
2791
2792 // Empty menus don't show up: https://bugreports.qt.io/browse/QTBUG-33728
2793 // We've added a placeholder in order to make sure some menus are visible.
2794 // Hide them as needed.
2795 if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_ANSI).length() > 0) {
2796 main_ui_->actionTelephonyANSIPlaceholder->setVisible(false);
2797 }
2798 if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_GSM).length() > 0) {
2799 main_ui_->actionTelephonyGSMPlaceholder->setVisible(false);
2800 }
2801 if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_LTE).length() > 0) {
2802 main_ui_->actionTelephonyLTEPlaceholder->setVisible(false);
2803 }
2804 if (wsApp->dynamicMenuGroupItems(REGISTER_STAT_GROUP_TELEPHONY_MTP3).length() > 0) {
2805 main_ui_->actionTelephonyMTP3Placeholder->setVisible(false);
2806 }
2807 }
2808
reloadDynamicMenus()2809 void MainWindow::reloadDynamicMenus()
2810 {
2811 foreach(register_stat_group_t menu_group, menu_groups) {
2812 QList<QAction *>actions = wsApp->removedMenuGroupItems(menu_group);
2813 removeMenuActions(actions, menu_group);
2814
2815 actions = wsApp->addedMenuGroupItems(menu_group);
2816 addMenuActions(actions, menu_group);
2817 }
2818
2819 wsApp->clearAddedMenuGroupItems();
2820 wsApp->clearRemovedMenuGroupItems();
2821 }
2822
externalMenuHelper(ext_menu_t * menu,QMenu * subMenu,gint depth)2823 void MainWindow::externalMenuHelper(ext_menu_t * menu, QMenu * subMenu, gint depth)
2824 {
2825 QAction * itemAction = Q_NULLPTR;
2826 ext_menubar_t * item = Q_NULLPTR;
2827 GList * children = Q_NULLPTR;
2828
2829 /* There must exists an xpath parent */
2830 Q_ASSERT(subMenu != NULL);
2831
2832 /* If the depth counter exceeds, something must have gone wrong */
2833 Q_ASSERT(depth < EXT_MENUBAR_MAX_DEPTH);
2834
2835 children = menu->children;
2836 /* Iterate the child entries */
2837 while (children && children->data) {
2838 item = gxx_list_data(ext_menubar_t *, children);
2839
2840 if (item->type == EXT_MENUBAR_MENU) {
2841 /* Handle Submenu entry */
2842 this->externalMenuHelper(item, subMenu->addMenu(item->label), depth++);
2843 } else if (item->type == EXT_MENUBAR_SEPARATOR) {
2844 subMenu->addSeparator();
2845 } else if (item->type == EXT_MENUBAR_ITEM || item->type == EXT_MENUBAR_URL) {
2846 itemAction = subMenu->addAction(item->name);
2847 itemAction->setData(QVariant::fromValue(static_cast<void *>(item)));
2848 itemAction->setText(item->label);
2849 connect(itemAction, SIGNAL(triggered()),
2850 this, SLOT(externalMenuItem_triggered()));
2851 }
2852
2853 /* Iterate Loop */
2854 children = gxx_list_next(children);
2855 }
2856 }
2857
searchSubMenu(QString objectName)2858 QMenu * MainWindow::searchSubMenu(QString objectName)
2859 {
2860 QList<QMenu*> lst;
2861
2862 if (objectName.length() > 0) {
2863 QString searchName = QString("menu") + objectName;
2864
2865 lst = main_ui_->menuBar->findChildren<QMenu*>();
2866 foreach(QMenu* m, lst) {
2867 if (QString::compare(m->objectName(), searchName) == 0)
2868 return m;
2869 }
2870 }
2871
2872 return 0;
2873 }
2874
addPluginIFStructures()2875 void MainWindow::addPluginIFStructures()
2876 {
2877 GList *user_menu = ext_menubar_get_entries();
2878
2879 while (user_menu && user_menu->data) {
2880 QMenu *subMenu = Q_NULLPTR;
2881 ext_menu_t *menu = gxx_list_data(ext_menu_t *, user_menu);
2882
2883 /* On this level only menu items should exist. Not doing an assert here,
2884 * as it could be an honest mistake */
2885 if (menu->type != EXT_MENUBAR_MENU) {
2886 user_menu = gxx_list_next(user_menu);
2887 continue;
2888 }
2889
2890 /* Create main submenu and add it to the menubar */
2891 if (menu->parent_menu) {
2892 QMenu *sortUnderneath = searchSubMenu(QString(menu->parent_menu));
2893 if (sortUnderneath)
2894 subMenu = sortUnderneath->addMenu(menu->label);
2895 }
2896
2897 if (!subMenu)
2898 subMenu = main_ui_->menuBar->addMenu(menu->label);
2899
2900 /* This will generate the action structure for each menu. It is recursive,
2901 * therefore a sub-routine, and we have a depth counter to prevent endless loops. */
2902 this->externalMenuHelper(menu, subMenu, 0);
2903
2904 /* Iterate Loop */
2905 user_menu = gxx_list_next(user_menu);
2906 }
2907
2908 int cntToolbars = 0;
2909
2910 QMenu *tbMenu = main_ui_->menuAdditionalToolbars;
2911 GList *if_toolbars = ext_toolbar_get_entries();
2912 while (if_toolbars && if_toolbars->data) {
2913 ext_toolbar_t *toolbar = gxx_list_data(ext_toolbar_t*, if_toolbars);
2914
2915 if (toolbar->type != EXT_TOOLBAR_BAR) {
2916 if_toolbars = gxx_list_next(if_toolbars);
2917 continue;
2918 }
2919
2920 bool visible = g_list_find_custom(recent.gui_additional_toolbars, toolbar->name, reinterpret_cast<GCompareFunc>(strcmp)) ? true : false;
2921
2922 AdditionalToolBar *ifToolBar = AdditionalToolBar::create(this, toolbar);
2923
2924 if (ifToolBar) {
2925 ifToolBar->setVisible(visible);
2926
2927 QAction *iftbAction = new QAction(QString(toolbar->name), this);
2928 iftbAction->setToolTip(toolbar->tooltip);
2929 iftbAction->setEnabled(true);
2930 iftbAction->setCheckable(true);
2931 iftbAction->setChecked(visible);
2932 iftbAction->setToolTip(tr("Show or hide the toolbar"));
2933 iftbAction->setData(VariantPointer<ext_toolbar_t>::asQVariant(toolbar));
2934
2935 QAction *before = Q_NULLPTR;
2936
2937 foreach(QAction *action, tbMenu->actions()) {
2938 /* Ensure we add the menu entries in sorted order */
2939 if (action->text().compare(toolbar->name, Qt::CaseInsensitive) > 0) {
2940 before = action;
2941 break;
2942 }
2943 }
2944
2945 tbMenu->insertAction(before, iftbAction);
2946
2947 addToolBar(Qt::TopToolBarArea, ifToolBar);
2948 insertToolBarBreak(ifToolBar);
2949
2950 if (show_hide_actions_)
2951 show_hide_actions_->addAction(iftbAction);
2952
2953 cntToolbars++;
2954 }
2955
2956 if_toolbars = gxx_list_next(if_toolbars);
2957 }
2958
2959 if (cntToolbars)
2960 tbMenu->menuAction()->setVisible(true);
2961 }
2962
removeAdditionalToolbar(QString toolbarName)2963 void MainWindow::removeAdditionalToolbar(QString toolbarName)
2964 {
2965 if (toolbarName.length() == 0)
2966 return;
2967
2968 QList<QToolBar *> toolbars = findChildren<QToolBar *>();
2969 foreach(QToolBar *tb, toolbars) {
2970 AdditionalToolBar *ifToolBar = dynamic_cast<AdditionalToolBar *>(tb);
2971
2972 if (ifToolBar && ifToolBar->menuName().compare(toolbarName)) {
2973 GList *entry = g_list_find_custom(recent.gui_additional_toolbars, qUtf8Printable(ifToolBar->menuName()), reinterpret_cast<GCompareFunc>(strcmp));
2974 if (entry) {
2975 recent.gui_additional_toolbars = g_list_remove(recent.gui_additional_toolbars, entry->data);
2976 }
2977 QList<QAction *> actions = main_ui_->menuAdditionalToolbars->actions();
2978 foreach(QAction *action, actions) {
2979 ext_toolbar_t *item = VariantPointer<ext_toolbar_t>::asPtr(action->data());
2980 if (item && ifToolBar->menuName().compare(item->name)) {
2981 if (show_hide_actions_)
2982 show_hide_actions_->removeAction(action);
2983 main_ui_->menuAdditionalToolbars->removeAction(action);
2984 }
2985 }
2986 break;
2987 }
2988 }
2989
2990 }
2991
getMwFileName()2992 QString MainWindow::getMwFileName()
2993 {
2994 return mwFileName_;
2995 }
2996
setMwFileName(QString fileName)2997 void MainWindow::setMwFileName(QString fileName)
2998 {
2999 mwFileName_ = fileName;
3000 return;
3001 }
3002
hasSelection()3003 bool MainWindow::hasSelection()
3004 {
3005 if (packet_list_)
3006 return packet_list_->multiSelectActive();
3007 return false;
3008 }
3009
selectedRows(bool useFrameNum)3010 QList<int> MainWindow::selectedRows(bool useFrameNum)
3011 {
3012 if (packet_list_)
3013 return packet_list_->selectedRows(useFrameNum);
3014 return QList<int>();
3015 }
3016
frameDataForRow(int row) const3017 frame_data * MainWindow::frameDataForRow(int row) const
3018 {
3019 if (packet_list_)
3020 return packet_list_->getFDataForRow(row);
3021
3022 return Q_NULLPTR;
3023 }
3024
3025 // Finds rtp id for selected stream and adds it to stream_ids
3026 // If reverse is set, tries to find reverse stream too
3027 // Return error string if error happens
3028 //
3029 // Note: Caller must free each returned rtpstream_info_t
findRtpStreams(QVector<rtpstream_id_t * > * stream_ids,bool reverse)3030 QString MainWindow::findRtpStreams(QVector<rtpstream_id_t *> *stream_ids, bool reverse)
3031 {
3032 rtpstream_tapinfo_t tapinfo;
3033 rtpstream_id_t *fwd_id, *rev_id;
3034 bool fwd_id_used, rev_id_used;
3035 const gchar filter_text[] = "rtp && rtp.version == 2 && rtp.ssrc && (ip || ipv6)";
3036 dfilter_t *sfcode;
3037 gchar *err_msg;
3038
3039 /* Try to get the hfid for "rtp.ssrc". */
3040 int hfid_rtp_ssrc = proto_registrar_get_id_byname("rtp.ssrc");
3041 if (hfid_rtp_ssrc == -1) {
3042 return tr("There is no \"rtp.ssrc\" field in this version of Wireshark.");
3043 }
3044
3045 /* Try to compile the filter. */
3046 if (!dfilter_compile(filter_text, &sfcode, &err_msg)) {
3047 QString err = QString(err_msg);
3048 g_free(err_msg);
3049 return err;
3050 }
3051
3052 if (!capture_file_.capFile() || !capture_file_.capFile()->current_frame) close();
3053
3054 if (!cf_read_current_record(capture_file_.capFile())) close();
3055
3056 frame_data *fdata = capture_file_.capFile()->current_frame;
3057
3058 epan_dissect_t edt;
3059
3060 epan_dissect_init(&edt, capture_file_.capFile()->epan, true, false);
3061 epan_dissect_prime_with_dfilter(&edt, sfcode);
3062 epan_dissect_prime_with_hfid(&edt, hfid_rtp_ssrc);
3063 epan_dissect_run(&edt, capture_file_.capFile()->cd_t,
3064 &capture_file_.capFile()->rec,
3065 frame_tvbuff_new_buffer(
3066 &capture_file_.capFile()->provider, fdata,
3067 &capture_file_.capFile()->buf),
3068 fdata, NULL);
3069
3070 /*
3071 * Packet must be an RTPv2 packet with an SSRC; we use the filter to
3072 * check.
3073 */
3074 if (!dfilter_apply_edt(sfcode, &edt)) {
3075 epan_dissect_cleanup(&edt);
3076 dfilter_free(sfcode);
3077 return tr("Please select an RTPv2 packet with an SSRC value");
3078 }
3079
3080 dfilter_free(sfcode);
3081
3082 /* We need the SSRC value of the current frame; try to get it. */
3083 GPtrArray *gp = proto_get_finfo_ptr_array(edt.tree, hfid_rtp_ssrc);
3084 if (gp == NULL || gp->len == 0) {
3085 /* XXX - should not happen, as the filter includes rtp.ssrc */
3086 epan_dissect_cleanup(&edt);
3087 return tr("SSRC value not found.");
3088 }
3089
3090 /*
3091 * OK, we have the SSRC value, so we can proceed.
3092 * Allocate RTP stream ID structures.
3093 */
3094 fwd_id = g_new0(rtpstream_id_t, 1);
3095 fwd_id_used = false;
3096 rev_id = g_new0(rtpstream_id_t, 1);
3097 rev_id_used = false;
3098
3099 /* Get the IP and port values for the forward direction. */
3100 rtpstream_id_copy_pinfo(&(edt.pi), fwd_id, false);
3101
3102 /* assume the inverse ip/port combination for the reverse direction */
3103 rtpstream_id_copy_pinfo(&(edt.pi), rev_id, true);
3104
3105 /* Save the SSRC value for the forward direction. */
3106 fwd_id->ssrc = fvalue_get_uinteger(&((field_info *)gp->pdata[0])->value);
3107
3108 epan_dissect_cleanup(&edt);
3109
3110 /* Register the tap listener */
3111 memset(&tapinfo, 0, sizeof(rtpstream_tapinfo_t));
3112 tapinfo.tap_data = this;
3113 tapinfo.mode = TAP_ANALYSE;
3114
3115 /* Scan for RTP streams (redissect all packets) */
3116 rtpstream_scan(&tapinfo, capture_file_.capFile(), Q_NULLPTR);
3117
3118 for (GList *strinfo_list = g_list_first(tapinfo.strinfo_list); strinfo_list; strinfo_list = gxx_list_next(strinfo_list)) {
3119 rtpstream_info_t * strinfo = gxx_list_data(rtpstream_info_t*, strinfo_list);
3120 if (rtpstream_id_equal(&(strinfo->id), fwd_id,RTPSTREAM_ID_EQUAL_NONE))
3121 {
3122 *stream_ids << fwd_id;
3123 fwd_id_used = true;
3124 }
3125
3126 if (rtpstream_id_equal(&(strinfo->id), rev_id,RTPSTREAM_ID_EQUAL_NONE))
3127 {
3128 if (rev_id->ssrc == 0) {
3129 rev_id->ssrc = strinfo->id.ssrc;
3130 }
3131 if (reverse) {
3132 *stream_ids << rev_id;
3133 rev_id_used = true;
3134 }
3135 }
3136 }
3137
3138 //
3139 // XXX - is it guaranteed that fwd_id and rev_id were both added to
3140 // *stream_ids? If so, this isn't necessary.
3141 //
3142 if (!fwd_id_used) {
3143 rtpstream_id_free(fwd_id);
3144 }
3145 if (!rev_id_used) {
3146 rtpstream_id_free(rev_id);
3147 }
3148 return NULL;
3149 }
3150
3151