Kerberos: add kerberos_inject_longterm_key() helper function
[wireshark-sm.git] / ui / qt / main_application.cpp
blob22528e4c65d67da038f41638eb1b5b5b7c1ea0cc
1 /* main_application.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
10 // warning C4267: 'argument' : conversion from 'size_t' to 'int', possible loss of data
11 #ifdef _MSC_VER
12 #pragma warning(push)
13 #pragma warning(disable : 4267)
14 #endif
16 #define WS_LOG_DOMAIN LOG_DOMAIN_MAIN
18 #include "main_application.h"
20 #include <algorithm>
21 #include <errno.h>
23 #include "wsutil/filesystem.h"
25 #include "epan/addr_resolv.h"
26 #include "epan/column-utils.h"
27 #include "epan/disabled_protos.h"
28 #include "epan/ftypes/ftypes.h"
29 #include "epan/prefs.h"
30 #include "epan/proto.h"
31 #include "epan/tap.h"
32 #include "epan/timestamp.h"
33 #include "epan/decode_as.h"
34 #include "epan/dfilter/dfilter-macro.h"
36 #include "ui/commandline.h"
37 #include "ui/decode_as_utils.h"
38 #include "ui/preference_utils.h"
39 #include "ui/iface_lists.h"
40 #include "ui/language.h"
41 #include "ui/recent.h"
42 #include "ui/simple_dialog.h"
43 #include "ui/util.h"
45 #include <ui/qt/utils/qt_ui_utils.h>
46 #include <ui/qt/utils/color_utils.h>
47 #include "coloring_rules_dialog.h"
49 #include "epan/color_filters.h"
50 #include "recent_file_status.h"
52 #include "extcap.h"
53 #ifdef HAVE_LIBPCAP
54 #include <capture/iface_monitor.h>
55 #endif
57 #include "wsutil/filter_files.h"
58 #include "ui/capture_globals.h"
59 #include "ui/software_update.h"
60 #include "ui/file_dialog.h"
61 #include "ui/recent_utils.h"
63 #ifdef HAVE_LIBPCAP
64 #include "ui/capture.h"
65 #endif
67 #include "wsutil/utf8_entities.h"
69 #ifdef _WIN32
70 # include "wsutil/file_util.h"
71 # include <QMessageBox>
72 # include <QSettings>
73 #endif /* _WIN32 */
75 #include <ui/qt/capture_file.h>
77 #include <ui/qt/main_window.h>
78 #include <ui/qt/main_status_bar.h>
80 #include <QAction>
81 #include <QApplication>
82 #include <QColorDialog>
83 #include <QDesktopServices>
84 #include <QDir>
85 #include <QEvent>
86 #include <QFileOpenEvent>
87 #include <QFontInfo>
88 #include <QFontMetrics>
89 #include <QLibraryInfo>
90 #include <QLocale>
91 #include <QMainWindow>
92 #include <QMutableListIterator>
93 #include <QSocketNotifier>
94 #include <QThreadPool>
95 #include <QUrl>
96 #include <qmath.h>
98 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
99 #include <QFontDatabase>
100 #endif
101 #include <QMimeDatabase>
103 #include <QStyleHints>
105 #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && defined(Q_OS_WIN)
106 #include <QStyleFactory>
107 #endif
109 #ifdef _MSC_VER
110 #pragma warning(pop)
111 #endif
113 MainApplication *mainApp;
115 // XXX - Copied from ui/gtk/file_dlg.c
117 static QList<recent_item_status *> recent_captures_;
118 static QHash<int, QList<QAction *> > dynamic_menu_groups_;
119 static QHash<int, QList<QAction *> > added_menu_groups_;
120 static QHash<int, QList<QAction *> > removed_menu_groups_;
122 QString MainApplication::window_title_separator_ = QString::fromUtf8(" " UTF8_MIDDLE_DOT " ");
124 // QMimeDatabase parses a large-ish XML file and can be slow to initialize.
125 // Do so in a worker thread as early as possible.
126 // https://github.com/lxde/pcmanfm-qt/issues/415
127 class MimeDatabaseInitThread : public QRunnable
129 private:
130 void run()
132 QMimeDatabase mime_db;
133 mime_db.mimeTypeForData(QByteArray());
137 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
138 // Populating the font database can be slow as well.
139 class FontDatabaseInitThread : public QRunnable
141 private:
142 void run()
144 QFontDatabase font_db;
147 #endif
149 void
150 topic_action(topic_action_e action)
152 if (mainApp) mainApp->helpTopicAction(action);
156 * Add the capture filename to the application-wide "Recent Files" list.
157 * Contrary to the name this isn't limited to the "recent" menu.
160 * XXX - We might want to call SHAddToRecentDocs under Windows 7:
161 * https://stackoverflow.com/questions/437212/how-do-you-register-a-most-recently-used-list-with-windows-in-preparation-for-win
163 extern "C" void
164 add_menu_recent_capture_file(const char *cf_name, bool force) {
165 QString normalized_cf_name = QString::fromUtf8(cf_name);
166 QDir cf_path;
168 cf_path.setPath(normalized_cf_name);
169 normalized_cf_name = cf_path.absolutePath();
170 normalized_cf_name = QDir::cleanPath(normalized_cf_name);
171 normalized_cf_name = QDir::toNativeSeparators(normalized_cf_name);
173 /* Iterate through the recent items list, removing duplicate entries and every
174 * item above count_max
176 unsigned int cnt = 1;
177 QMutableListIterator<recent_item_status *> rii(recent_captures_);
178 while (rii.hasNext()) {
179 recent_item_status *ri = rii.next();
180 /* if this element string is one of our special items (separator, ...) or
181 * already in the list or
182 * this element is above maximum count (too old), remove it
184 if (ri->filename.length() < 1 ||
185 #ifdef _WIN32
186 /* do a case insensitive compare on win32 */
187 ri->filename.compare(normalized_cf_name, Qt::CaseInsensitive) == 0 ||
188 #else /* _WIN32 */
190 * Do a case sensitive compare on UN*Xes.
192 * XXX - on UN*Xes such as macOS, where you can use pathconf()
193 * to check whether a given file system is case-sensitive or
194 * not, we should check whether this particular file system
195 * is case-sensitive and do the appropriate comparison.
197 ri->filename.compare(normalized_cf_name) == 0 ||
198 #endif
199 (!force && cnt >= prefs.gui_recent_files_count_max)) {
200 rii.remove();
201 delete(ri);
202 cnt--;
204 cnt++;
206 mainApp->addRecentItem(normalized_cf_name, 0, false);
209 /* write all capture filenames of the menu to the user's recent file */
210 extern "C" void menu_recent_file_write_all(FILE *rf) {
212 /* we have to iterate backwards through the children's list,
213 * so we get the latest item last in the file.
215 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
216 int i = qMin(recent_captures_.size(), (int)(prefs.gui_recent_files_count_max)) - 1;
217 #else
218 qsizetype i = qMin(recent_captures_.size(), (qsizetype)prefs.gui_recent_files_count_max) - 1;
219 #endif
220 for (; i >= 0; i--) {
221 recent_item_status *ri = recent_captures_.at(i);
222 /* get capture filename from the menu item label */
223 QString cf_name = ri->filename;
224 if (!cf_name.isNull()) {
225 fprintf (rf, RECENT_KEY_CAPTURE_FILE ": %s\n", qUtf8Printable(cf_name));
230 #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN)
231 /** Check to see if Wireshark can shut down safely (e.g. offer to save the
232 * current capture).
234 extern "C" int software_update_can_shutdown_callback(void) {
235 return mainApp->softwareUpdateCanShutdown();
238 /** Shut down Wireshark in preparation for an upgrade.
240 extern "C" void software_update_shutdown_request_callback(void) {
241 mainApp->softwareUpdateShutdownRequest();
243 #endif // HAVE_SOFTWARE_UPDATE && Q_OS_WIN
245 // Check each recent item in a separate thread so that we don't hang while
246 // calling stat(). This is called periodically because files and entire
247 // volumes can disappear and reappear at any time.
248 void MainApplication::refreshRecentCaptures() {
249 recent_item_status *ri;
250 RecentFileStatus *rf_status;
252 // We're in the middle of a capture. Don't create traffic.
253 if (active_captures_ > 0) return;
255 foreach (ri, recent_captures_) {
256 if (ri->in_thread) {
257 continue;
259 rf_status = new RecentFileStatus(ri->filename, this);
260 QThreadPool::globalInstance()->start(rf_status);
264 void MainApplication::refreshPacketData()
266 if (host_name_lookup_process()) {
267 emit addressResolutionChanged();
268 } else if (col_data_changed()) {
269 emit columnDataChanged();
273 #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && defined(Q_OS_WIN)
274 void MainApplication::colorSchemeChanged() {
275 if (ColorUtils::themeIsDark()) {
276 setStyle(QStyleFactory::create("fusion"));
277 } else {
278 setStyle(QStyleFactory::create("windowsvista"));
281 #endif
283 void MainApplication::updateTaps()
285 draw_tap_listeners(false);
288 QDir MainApplication::openDialogInitialDir() {
289 return QDir(get_open_dialog_initial_dir());
292 void MainApplication::setLastOpenDirFromFilename(const QString file_name)
294 /* XXX - Use canonicalPath() instead of absolutePath()? */
295 QString directory = QDir::toNativeSeparators(QFileInfo(file_name).absolutePath());
296 /* XXX - printable? */
297 set_last_open_dir(qUtf8Printable(directory));
300 void MainApplication::helpTopicAction(topic_action_e action)
302 QString url = gchar_free_to_qstring(topic_action_url(action));
304 if (!url.isEmpty()) {
305 QDesktopServices::openUrl(QUrl(QDir::fromNativeSeparators(url)));
309 const QFont MainApplication::monospaceFont(bool zoomed) const
311 if (zoomed) {
312 return zoomed_font_;
314 return mono_font_;
317 void MainApplication::setMonospaceFont(const char *font_string) {
319 if (font_string && strlen(font_string) > 0) {
320 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
321 // Qt 6's QFont::toString returns a value with 16 or 17 fields, e.g.
322 // Consolas,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1
323 // Corbel,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,Regular
324 // Qt 5's QFont::fromString expects a value with 10 or 11 fields, e.g.
325 // Consolas,10,-1,5,50,0,0,0,0,0
326 // Corbel,10,-1,5,50,0,0,0,0,0,Regular
327 // It looks like Qt6's QFont::fromString can read both forms:
328 // https://github.com/qt/qtbase/blob/6.0/src/gui/text/qfont.cpp#L2146
329 // but Qt5's cannot:
330 // https://github.com/qt/qtbase/blob/5.15/src/gui/text/qfont.cpp#L2151
331 const char *fs_ptr = font_string;
332 int field_count = 1;
333 while ((fs_ptr = strchr(fs_ptr, ',')) != NULL) {
334 fs_ptr++;
335 field_count++;
337 if (field_count <= 11) {
338 #endif
339 mono_font_.fromString(font_string);
341 // Only accept the font name if it actually exists.
342 if (mono_font_.family() == QFontInfo(mono_font_).family()) {
343 return;
344 } else {
345 ws_warning("Monospace font family %s differs from its fontinfo: %s",
346 qUtf8Printable(mono_font_.family()), qUtf8Printable(QFontInfo(mono_font_).family()));
348 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
349 } else {
350 ws_warning("Monospace font %s appears to be from Qt6 and we're running Qt5.", font_string);
352 #endif
355 // https://en.wikipedia.org/wiki/Category:Monospaced_typefaces
356 const char *win_default_font = "Consolas";
357 const char *win_alt_font = "Lucida Console";
358 // SF Mono might be a system font someday. Right now (Oct 2016) it appears
359 // to be limited to Xcode and Terminal.
360 // http://www.openradar.me/26790072
361 // http://www.openradar.me/26862220
362 const char *osx_default_font = "SF Mono";
363 const QStringList osx_alt_fonts = QStringList() << "Menlo" << "Monaco";
364 // XXX Detect Ubuntu systems (e.g. via /etc/os-release and/or
365 // /etc/lsb_release) and add "Ubuntu Mono Regular" there.
366 // https://design.ubuntu.com/font/
367 const char *x11_default_font = "Liberation Mono";
368 const QStringList x11_alt_fonts = QStringList() << "DejaVu Sans Mono" << "Bitstream Vera Sans Mono";
369 const QStringList fallback_fonts = QStringList() << "Lucida Sans Typewriter" << "Inconsolata" << "Droid Sans Mono" << "Andale Mono" << "Courier New" << "monospace";
370 QStringList substitutes;
371 int font_size_adjust = 0;
373 // Try to pick the latest, shiniest fixed-width font for our OS.
374 #if defined(Q_OS_WIN)
375 const char *default_font = win_default_font;
376 substitutes << win_alt_font << osx_default_font << osx_alt_fonts << x11_default_font << x11_alt_fonts << fallback_fonts;
377 # if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
378 font_size_adjust = 1;
379 # else // QT_VERSION
380 font_size_adjust = 2;
381 # endif // QT_VERSION
382 #elif defined(Q_OS_MAC)
383 const char *default_font = osx_default_font;
384 substitutes << osx_alt_fonts << win_default_font << win_alt_font << x11_default_font << x11_alt_fonts << fallback_fonts;
385 #else // Q_OS
386 const char *default_font = x11_default_font;
387 substitutes << x11_alt_fonts << win_default_font << win_alt_font << osx_default_font << osx_alt_fonts << fallback_fonts;
388 #endif // Q_OS
390 mono_font_ = QFont(default_font, mainApp->font().pointSize() + font_size_adjust);
391 mono_font_.insertSubstitutions(default_font, substitutes);
392 mono_font_.setBold(false);
394 // Retrieve the effective font and apply it.
395 mono_font_.setFamily(QFontInfo(mono_font_).family());
397 g_free(prefs.gui_font_name);
398 prefs.gui_font_name = qstring_strdup(mono_font_.toString());
401 int MainApplication::monospaceTextSize(const char *str)
403 return QFontMetrics(mono_font_).horizontalAdvance(str);
406 void MainApplication::setConfigurationProfile(const char *profile_name, bool write_recent_file)
408 char *rf_path;
409 int rf_open_errno;
410 char *err_msg = NULL;
412 bool prev_capture_no_interface_load;
413 bool prev_capture_no_extcap;
415 /* First check if profile exists */
416 if (!profile_exists(profile_name, false)) {
417 if (profile_exists(profile_name, true)) {
418 char *pf_dir_path, *pf_dir_path2, *pf_filename;
419 /* Copy from global profile */
420 if (create_persconffile_profile(profile_name, &pf_dir_path) == -1) {
421 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
422 "Can't create directory\n\"%s\":\n%s.",
423 pf_dir_path, g_strerror(errno));
425 g_free(pf_dir_path);
428 if (copy_persconffile_profile(profile_name, profile_name, true, &pf_filename,
429 &pf_dir_path, &pf_dir_path2) == -1) {
430 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK,
431 "Can't copy file \"%s\" in directory\n\"%s\" to\n\"%s\":\n%s.",
432 pf_filename, pf_dir_path2, pf_dir_path, g_strerror(errno));
434 g_free(pf_filename);
435 g_free(pf_dir_path);
436 g_free(pf_dir_path2);
438 } else {
439 /* No personal and no global profile exists */
440 return;
444 /* Then check if changing to another profile */
445 if (profile_name && strcmp (profile_name, get_profile_name()) == 0) {
446 return;
449 prev_capture_no_interface_load = prefs.capture_no_interface_load;
450 prev_capture_no_extcap = prefs.capture_no_extcap;
452 /* Get the current geometry, before writing it to disk */
453 emit profileChanging();
455 if (write_recent_file && profile_exists(get_profile_name(), false))
457 /* Write recent file for profile we are leaving, if it still exists */
458 write_profile_recent();
461 /* Set profile name and update the status bar */
462 set_profile_name (profile_name);
463 emit profileNameChanged(profile_name);
465 /* Apply new preferences */
466 readConfigurationFiles(true);
468 /* Apply command-line preferences */
469 commandline_options_reapply();
470 extcap_register_preferences();
472 /* Switching profile requires reloading the macro list. */
473 reloadDisplayFilterMacros();
475 if (!recent_read_profile_static(&rf_path, &rf_open_errno)) {
476 simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK,
477 "Could not open common recent file\n\"%s\": %s.",
478 rf_path, g_strerror(rf_open_errno));
479 g_free(rf_path);
481 if (recent.gui_fileopen_remembered_dir &&
482 test_for_directory(recent.gui_fileopen_remembered_dir) == EISDIR) {
483 set_last_open_dir(recent.gui_fileopen_remembered_dir);
485 timestamp_set_type(recent.gui_time_format);
486 timestamp_set_precision(recent.gui_time_precision);
487 timestamp_set_seconds_type (recent.gui_seconds_format);
488 tap_update_timer_.setInterval(prefs.tap_update_interval);
490 prefs_to_capture_opts();
491 prefs_apply_all();
492 #ifdef HAVE_LIBPCAP
493 update_local_interfaces();
494 #endif
496 setMonospaceFont(prefs.gui_font_name);
497 ColorUtils::setScheme(prefs.gui_color_scheme);
499 // Freeze the packet list early to avoid updating column data before doing a
500 // full redissection. The packet list will be thawed when redissection is done.
501 emit freezePacketList(true);
503 emit columnsChanged();
504 emit colorsChanged();
505 emit preferencesChanged();
506 emit recentPreferencesRead();
507 emit filterExpressionsChanged();
508 emit checkDisplayFilter();
509 emit captureFilterListChanged();
510 emit displayFilterListChanged();
512 /* Reload color filters */
513 if (!color_filters_reload(&err_msg, color_filter_add_cb)) {
514 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err_msg);
515 g_free(err_msg);
518 /* Load interfaces if settings have changed */
519 if (!prefs.capture_no_interface_load &&
520 ((prefs.capture_no_interface_load != prev_capture_no_interface_load) ||
521 (prefs.capture_no_extcap != prev_capture_no_extcap))) {
522 refreshLocalInterfaces();
525 emit localInterfaceListChanged();
526 emit packetDissectionChanged();
528 /* Write recent_common file to ensure last used profile setting is stored. */
529 write_recent();
532 void MainApplication::reloadLuaPluginsDelayed()
534 QTimer::singleShot(0, this, &MainApplication::reloadLuaPlugins);
537 const QIcon &MainApplication::normalIcon()
539 if (normal_icon_.isNull()) {
540 initializeIcons();
542 return normal_icon_;
545 const QIcon &MainApplication::captureIcon()
547 if (capture_icon_.isNull()) {
548 initializeIcons();
550 return capture_icon_;
553 const QString MainApplication::windowTitleString(QStringList title_parts)
555 QMutableStringListIterator tii(title_parts);
556 while (tii.hasNext()) {
557 QString ti = tii.next();
558 if (ti.isEmpty()) tii.remove();
560 title_parts.prepend(applicationName());
561 return title_parts.join(window_title_separator_);
564 void MainApplication::applyCustomColorsFromRecent()
566 int i = 0;
567 bool ok;
568 for (GList *custom_color = recent.custom_colors; custom_color; custom_color = custom_color->next) {
569 QRgb rgb = QString((const char *)custom_color->data).toUInt(&ok, 16);
570 if (ok) {
571 QColorDialog::setCustomColor(i++, QColor(rgb));
576 // Return the first top-level QMainWindow.
577 QWidget *MainApplication::mainWindow()
579 foreach (QWidget *tlw, topLevelWidgets()) {
580 QMainWindow *tlmw = qobject_cast<QMainWindow *>(tlw);
581 if (tlmw && tlmw->isVisible()) {
582 return tlmw;
585 return 0;
588 void MainApplication::storeCustomColorsInRecent()
590 if (QColorDialog::customCount()) {
591 prefs_clear_string_list(recent.custom_colors);
592 recent.custom_colors = NULL;
593 for (int i = 0; i < QColorDialog::customCount(); i++) {
594 QRgb rgb = QColorDialog::customColor(i).rgb();
595 recent.custom_colors = g_list_append(recent.custom_colors, ws_strdup_printf("%08x", rgb));
600 bool MainApplication::event(QEvent *event)
602 QString display_filter = NULL;
603 if (event->type() == QEvent::FileOpen) {
604 QFileOpenEvent *foe = static_cast<QFileOpenEvent *>(event);
605 if (foe && foe->file().length() > 0) {
606 QString cf_path(foe->file());
607 if (initialized_) {
608 emit openCaptureFile(cf_path, display_filter, WTAP_TYPE_AUTO);
609 } else {
610 pending_open_files_.append(cf_path);
613 return true;
615 return QApplication::event(event);
618 void MainApplication::clearRecentCaptures() {
619 qDeleteAll(recent_captures_);
620 recent_captures_.clear();
621 emit updateRecentCaptureStatus(NULL, 0, false);
624 void MainApplication::cleanup()
626 software_update_cleanup();
627 storeCustomColorsInRecent();
628 // Write the user's recent file(s) to disk.
629 write_profile_recent();
630 write_recent();
632 qDeleteAll(recent_captures_);
633 recent_captures_.clear();
634 // We might end up here via exit_application.
635 QThreadPool::globalInstance()->waitForDone();
638 void MainApplication::itemStatusFinished(const QString filename, qint64 size, bool accessible) {
639 recent_item_status *ri;
641 foreach (ri, recent_captures_) {
642 if (filename == ri->filename && (size != ri->size || accessible != ri->accessible)) {
643 ri->size = size;
644 ri->accessible = accessible;
645 ri->in_thread = false;
647 emit updateRecentCaptureStatus(filename, size, accessible);
652 MainApplication::MainApplication(int &argc, char **argv) :
653 QApplication(argc, argv),
654 initialized_(false),
655 is_reloading_lua_(false),
656 if_notifier_(NULL),
657 active_captures_(0),
658 refresh_interfaces_pending_(false)
659 #ifdef HAVE_LIBPCAP
660 , cached_if_list_(NULL)
661 #endif
663 mainApp = this;
665 MimeDatabaseInitThread *mime_db_init_thread = new(MimeDatabaseInitThread);
666 QThreadPool::globalInstance()->start(mime_db_init_thread);
667 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
668 FontDatabaseInitThread *font_db_init_thread = new (FontDatabaseInitThread);
669 QThreadPool::globalInstance()->start(font_db_init_thread);
670 #endif
672 Q_INIT_RESOURCE(about);
673 Q_INIT_RESOURCE(i18n);
674 Q_INIT_RESOURCE(layout);
675 Q_INIT_RESOURCE(stock_icons);
676 Q_INIT_RESOURCE(languages);
678 #ifdef Q_OS_WIN
679 /* RichEd20.DLL is needed for native file dialog filter entries. */
680 ws_load_library("riched20.dll");
681 #endif // Q_OS_WIN
683 #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
684 setAttribute(Qt::AA_UseHighDpiPixmaps);
685 #endif
687 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
688 setAttribute(Qt::AA_DisableWindowContextHelpButton);
689 #endif
691 // Throw various settings at the wall with the hope that one of them will
692 // enable context menu shortcuts QTBUG-69452, QTBUG-109590
693 setAttribute(Qt::AA_DontShowShortcutsInContextMenus, false);
694 styleHints()->setShowShortcutsInContextMenus(true);
697 // XXX - this means we try to check for the existence of all files
698 // in the recent list every 2 seconds; that causes noticeable network
699 // traffic if any of them are stored on file servers.
701 // QFileSystemWatcher should allow us to watch for files being
702 // removed or renamed. It uses kqueues and EVFILT_VNODE on FreeBSD,
703 // NetBSD, FSEvents on macOS, inotify on Linux if available, and
704 // FindFirstChangeNotification() on Windows. On all other platforms,
705 // it just periodically polls, as we're doing now.
707 // For unmounts:
709 // macOS and FreeBSD deliver NOTE_REVOKE notes for EVFILT_VNODE, and
710 // QFileSystemWatcher delivers signals for them, just as it does for
711 // NOTE_DELETE and NOTE_RENAME.
713 // On Linux, inotify:
715 // http://man7.org/linux/man-pages/man7/inotify.7.html
717 // appears to deliver "filesystem containing watched object was
718 // unmounted" events. It looks as if Qt turns them into "changed"
719 // events.
721 // On Windows, it's not clearly documented what happens on a handle
722 // opened with FindFirstChangeNotification() if the volume on which
723 // the path handed to FindFirstChangeNotification() is removed, or
724 // ejected, or whatever the Windowsese is for "unmounted". The
725 // handle obviously isn't valid any more, but whether it just hangs
726 // around and never delivers any notifications or delivers an
727 // event that turns into an error indication doesn't seem to be
728 // documented. If it just hangs around, I think our main loop will
729 // receive a WM_DEVICECHANGE Windows message with DBT_DEVICEREMOVECOMPLETE
730 // if an unmount occurs - even for network devices. If we need to watch
731 // for those, we can use the winEvent method of the QWidget for the
732 // top-level window to get Windows messages.
734 // Note also that remote file systems might not report file
735 // removal or renames if they're done on the server or done by
736 // another client. At least on macOS, they *will* get reported
737 // if they're done on the machine running the program doing the
738 // kqueue stuff, and, at least in newer versions, should get
739 // reported on SMB-mounted (and AFP-mounted?) file systems
740 // even if done on the server or another client.
742 // But, when push comes to shove, the file manager(s) on the
743 // OSes in question probably use the same mechanisms to
744 // monitor folders in folder windows or open/save dialogs or...,
745 // so my inclination is just to use QFileSystemWatcher.
747 // However, that wouldn't catch files that become *re*-accessible
748 // by virtue of a file system being re-mounted. The only way to
749 // catch *that* would be to watch for mounts and re-check all
750 // marked-as-inaccessible files.
752 // macOS and FreeBSD also support EVFILT_FS events, which notify you
753 // of file system mounts and unmounts. We'd need to add our own
754 // kqueue for that, if we can check those with QSocketNotifier.
756 // On Linux, at least as of 2006, you're supposed to poll /proc/mounts:
758 // https://lkml.org/lkml/2006/2/22/169
760 // to discover mounts.
762 // On Windows, you'd probably have to watch for WM_DEVICECHANGE events.
764 // Then again, with an automounter, a file system containing a
765 // recent capture might get unmounted automatically if you haven't
766 // referred to anything on that file system for a while, and get
767 // treated as inaccessible. However, if you try to access it,
768 // the automounter will attempt to re-mount it, so the access *will*
769 // succeed if the automounter can remount the file.
771 // (Speaking of automounters, repeatedly polling recent files will
772 // keep the file system from being unmounted, for what that's worth.)
774 // At least on macOS, you can determine whether a file is on an
775 // automounted file system by calling statfs() on its path and
776 // checking whether MNT_AUTOMOUNTED is set in f_flags. FreeBSD
777 // appears to support that flag as well, but no other *BSD appears
778 // to.
780 // I'm not sure what can be done on Linux.
782 recent_timer_.setParent(this);
783 connect(&recent_timer_, &QTimer::timeout, this, &MainApplication::refreshRecentCaptures);
784 recent_timer_.start(2000);
786 packet_data_timer_.setParent(this);
787 connect(&packet_data_timer_, &QTimer::timeout, this, &MainApplication::refreshPacketData);
788 packet_data_timer_.start(1000);
790 tap_update_timer_.setParent(this);
791 tap_update_timer_.setInterval(TAP_UPDATE_DEFAULT_INTERVAL);
792 connect(this, &MainApplication::appInitialized, &tap_update_timer_, [&]() { tap_update_timer_.start(); });
793 connect(&tap_update_timer_, &QTimer::timeout, this, &MainApplication::updateTaps);
795 // Application-wide style sheet
796 QString app_style_sheet = qApp->styleSheet();
797 qApp->setStyleSheet(app_style_sheet);
799 // If our window text is lighter than the window background, assume the theme is dark.
800 prefs_set_gui_theme_is_dark(ColorUtils::themeIsDark());
802 #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN)
803 connect(this, &MainApplication::softwareUpdateQuit, this, &MainApplication::quit, Qt::QueuedConnection);
804 #endif
806 #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && defined(Q_OS_WIN)
807 colorSchemeChanged();
808 connect(styleHints(), &QStyleHints::colorSchemeChanged, this, &MainApplication::colorSchemeChanged);
809 #endif
811 connect(qApp, &QApplication::aboutToQuit, this, &MainApplication::cleanup);
814 MainApplication::~MainApplication()
816 mainApp = NULL;
817 #ifdef HAVE_LIBPCAP
818 free_interface_list(cached_if_list_);
819 #endif
820 clearDynamicMenuGroupItems();
823 void MainApplication::registerUpdate(register_action_e action, const char *message)
825 emit splashUpdate(action, message);
828 void MainApplication::emitAppSignal(AppSignal signal)
830 switch (signal) {
831 case ColumnsChanged:
832 emit columnsChanged();
833 break;
834 case CaptureFilterListChanged:
835 emit captureFilterListChanged();
836 break;
837 case DisplayFilterListChanged:
838 emit displayFilterListChanged();
839 break;
840 case FilterExpressionsChanged:
841 emit filterExpressionsChanged();
842 break;
843 case LocalInterfacesChanged:
844 emit localInterfaceListChanged();
845 break;
846 case NameResolutionChanged:
847 emit addressResolutionChanged();
848 break;
849 case PreferencesChanged:
850 emit preferencesChanged();
851 break;
852 case PacketDissectionChanged:
853 emit packetDissectionChanged();
854 break;
855 case ProfileChanging:
856 emit profileChanging();
857 break;
858 case RecentCapturesChanged:
859 emit updateRecentCaptureStatus(NULL, 0, false);
860 break;
861 case RecentPreferencesRead:
862 emit recentPreferencesRead();
863 break;
864 case FieldsChanged:
865 emit fieldsChanged();
866 break;
867 case ColorsChanged:
868 emit colorsChanged();
869 break;
870 case FreezePacketList:
871 emit freezePacketList(false);
872 break;
873 default:
874 break;
878 // Flush any collected app signals.
880 // On macOS emitting PacketDissectionChanged from a dialog can
881 // render the application unusable:
882 // https://gitlab.com/wireshark/wireshark/-/issues/11361
883 // https://gitlab.com/wireshark/wireshark/-/issues/11448
884 // Work around the problem by queueing up app signals and emitting them
885 // after the dialog is closed.
887 // The following bugs might be related although they don't describe the
888 // exact behavior we're working around here:
889 // https://bugreports.qt.io/browse/QTBUG-38512
890 // https://bugreports.qt.io/browse/QTBUG-38600
891 void MainApplication::flushAppSignals()
893 while (!app_signals_.isEmpty()) {
894 mainApp->emitAppSignal(app_signals_.takeFirst());
898 void MainApplication::emitStatCommandSignal(const QString &menu_path, const char *arg, void *userdata)
900 emit openStatCommandDialog(menu_path, arg, userdata);
903 void MainApplication::emitTapParameterSignal(const QString cfg_abbr, const QString arg, void *userdata)
905 emit openTapParameterDialog(cfg_abbr, arg, userdata);
908 // XXX Combine statistics and funnel routines into addGroupItem + groupItems?
909 void MainApplication::addDynamicMenuGroupItem(int group, QAction *sg_action)
911 if (!dynamic_menu_groups_.contains(group)) {
912 dynamic_menu_groups_[group] = QList<QAction *>();
914 dynamic_menu_groups_[group] << sg_action;
917 void MainApplication::appendDynamicMenuGroupItem(int group, QAction *sg_action)
919 if (!added_menu_groups_.contains(group)) {
920 added_menu_groups_[group] = QList<QAction *>();
922 added_menu_groups_[group] << sg_action;
923 addDynamicMenuGroupItem(group, sg_action);
926 void MainApplication::removeDynamicMenuGroupItem(int group, QAction *sg_action)
928 if (!removed_menu_groups_.contains(group)) {
929 removed_menu_groups_[group] = QList<QAction *>();
931 removed_menu_groups_[group] << sg_action;
932 dynamic_menu_groups_[group].removeAll(sg_action);
935 void MainApplication::clearDynamicMenuGroupItems()
937 foreach (int group, dynamic_menu_groups_.keys()) {
938 dynamic_menu_groups_[group].clear();
942 QList<QAction *> MainApplication::dynamicMenuGroupItems(int group)
944 if (!dynamic_menu_groups_.contains(group)) {
945 return QList<QAction *>();
948 QList<QAction *> sgi_list = dynamic_menu_groups_[group];
949 std::sort(sgi_list.begin(), sgi_list.end(), qActionLessThan);
950 return sgi_list;
953 QList<QAction *> MainApplication::addedMenuGroupItems(int group)
955 if (!added_menu_groups_.contains(group)) {
956 return QList<QAction *>();
959 QList<QAction *> sgi_list = added_menu_groups_[group];
960 std::sort(sgi_list.begin(), sgi_list.end(), qActionLessThan);
961 return sgi_list;
964 QList<QAction *> MainApplication::removedMenuGroupItems(int group)
966 if (!removed_menu_groups_.contains(group)) {
967 return QList<QAction *>();
970 QList<QAction *> sgi_list = removed_menu_groups_[group];
971 std::sort(sgi_list.begin(), sgi_list.end(), qActionLessThan);
972 return sgi_list;
975 void MainApplication::clearAddedMenuGroupItems()
977 foreach (int group, added_menu_groups_.keys()) {
978 added_menu_groups_[group].clear();
982 void MainApplication::clearRemovedMenuGroupItems()
984 foreach (int group, removed_menu_groups_.keys()) {
985 foreach (QAction *action, removed_menu_groups_[group]) {
986 delete action;
988 removed_menu_groups_[group].clear();
992 #ifdef HAVE_LIBPCAP
994 static void
995 iface_mon_event_cb(const char *iface, int added, int up)
997 int present = 0;
998 unsigned ifs, j;
999 interface_t *device;
1000 interface_options *interface_opts;
1002 for (ifs = 0; ifs < global_capture_opts.all_ifaces->len; ifs++) {
1003 device = &g_array_index(global_capture_opts.all_ifaces, interface_t, ifs);
1004 if (strcmp(device->name, iface) == 0) {
1005 present = 1;
1006 if (!up) {
1008 * Interface went down or disappeared; remove all instances
1009 * of it from the current list of interfaces selected
1010 * for capturing.
1012 for (j = 0; j < global_capture_opts.ifaces->len; j++) {
1013 interface_opts = &g_array_index(global_capture_opts.ifaces, interface_options, j);
1014 if (strcmp(interface_opts->name, device->name) == 0) {
1015 capture_opts_del_iface(&global_capture_opts, j);
1022 mainApp->emitLocalInterfaceEvent(iface, added, up);
1023 if (present != up) {
1025 * We've been told that there's a new interface or that an old
1026 * interface is gone; reload the local interface list.
1028 * XXX: We also want to reload the local interface list if [what
1029 * we can retrieve about] the capabilities of the device have changed.
1030 * Ideally we'd update the capabilities of just the one device in
1031 * the cache and signal that the list has been updated, instead of
1032 * freeing the entire cache and scanning again - but some extcaps
1033 * depend on other interfaces being up; e.g. by default androiddump
1034 * tries to connect to the loopback interface to look for adb running,
1035 * so if the loopback interface changes so does the status of
1036 * androiddump.
1038 * On Linux, at least, you can't get the capabilities from a down
1039 * interface, but it's still present in all_ifaces - dumpcap returns
1040 * it in the list, and we show it so the user can get a status / error
1041 * message when trying to capture on it instead of it vanishing.
1042 * So if both present and up are true, then we still want to refresh
1043 * to update the capabilities and restart the stats.
1045 * We also store the address in all_ifaces and show them to the user,
1046 * so we probably should monitor those events as well and update
1047 * the interface list appropriately when those change.
1049 mainApp->refreshLocalInterfaces();
1053 #endif
1055 void MainApplication::ifChangeEventsAvailable()
1057 #ifdef HAVE_LIBPCAP
1059 * Something's readable from the descriptor for interface
1060 * monitoring.
1062 * Have the interface-monitoring code Read whatever interface-change
1063 * events are available, and call the callback for them.
1065 iface_mon_event();
1066 #endif
1069 void MainApplication::emitLocalInterfaceEvent(const char *ifname, int added, int up)
1071 emit localInterfaceEvent(ifname, added, up);
1074 void MainApplication::refreshLocalInterfaces()
1076 if (active_captures_ > 0) {
1077 refresh_interfaces_pending_ = true;
1078 return;
1081 refresh_interfaces_pending_ = false;
1082 extcap_clear_interfaces();
1084 #ifdef HAVE_LIBPCAP
1085 emit scanLocalInterfaces(nullptr);
1086 #endif
1089 #ifdef HAVE_LIBPCAP
1090 GList* MainApplication::getInterfaceList() const
1092 return interface_list_copy(cached_if_list_);
1095 void MainApplication::setInterfaceList(GList *if_list)
1097 free_interface_list(cached_if_list_);
1098 cached_if_list_ = interface_list_copy(if_list);
1100 #endif
1102 void MainApplication::allSystemsGo()
1104 QString display_filter = NULL;
1105 initialized_ = true;
1106 emit appInitialized();
1107 while (pending_open_files_.length() > 0) {
1108 emit openCaptureFile(pending_open_files_.front(), display_filter, WTAP_TYPE_AUTO);
1109 pending_open_files_.pop_front();
1111 software_update_init();
1113 #ifdef HAVE_LIBPCAP
1114 int err;
1115 err = iface_mon_start(&iface_mon_event_cb);
1116 if (err == 0) {
1117 if_notifier_ = new QSocketNotifier(iface_mon_get_sock(),
1118 QSocketNotifier::Read, this);
1119 connect(if_notifier_, &QSocketNotifier::activated, this, &MainApplication::ifChangeEventsAvailable);
1121 #endif
1124 _e_prefs *MainApplication::readConfigurationFiles(bool reset)
1126 e_prefs *prefs_p;
1128 if (reset) {
1130 // Reset current preferences and enabled/disabled protocols and
1131 // heuristic dissectors before reading.
1132 // (Needed except when this is called at startup.)
1134 prefs_reset();
1135 proto_reenable_all();
1138 /* Load libwireshark settings from the current profile. */
1139 prefs_p = epan_load_settings();
1141 return prefs_p;
1144 QList<recent_item_status *> MainApplication::recentItems() const {
1145 return recent_captures_;
1148 void MainApplication::addRecentItem(const QString filename, qint64 size, bool accessible) {
1149 recent_item_status *ri = new(recent_item_status);
1151 ri->filename = filename;
1152 ri->size = size;
1153 ri->accessible = accessible;
1154 ri->in_thread = false;
1155 recent_captures_.prepend(ri);
1157 itemStatusFinished(filename, size, accessible);
1160 void MainApplication::removeRecentItem(const QString &filename)
1162 QMutableListIterator<recent_item_status *> rii(recent_captures_);
1164 while (rii.hasNext()) {
1165 recent_item_status *ri = rii.next();
1166 #ifdef _WIN32
1167 /* Do a case insensitive compare on win32 */
1168 if (ri->filename.compare(filename, Qt::CaseInsensitive) == 0) {
1169 #else
1170 /* Do a case sensitive compare on UN*Xes.
1172 * XXX - on UN*Xes such as macOS, where you can use pathconf()
1173 * to check whether a given file system is case-sensitive or
1174 * not, we should check whether this particular file system
1175 * is case-sensitive and do the appropriate comparison.
1177 if (ri->filename.compare(filename) == 0) {
1178 #endif
1179 rii.remove();
1180 delete(ri);
1184 emit updateRecentCaptureStatus(NULL, 0, false);
1187 static void switchTranslator(QTranslator& myTranslator, const QString& filename,
1188 const QString& searchPath)
1190 mainApp->removeTranslator(&myTranslator);
1192 if (myTranslator.load(filename, searchPath))
1193 mainApp->installTranslator(&myTranslator);
1196 void MainApplication::loadLanguage(const QString newLanguage)
1198 QLocale locale;
1199 QString localeLanguage;
1201 if (newLanguage.isEmpty() || newLanguage == USE_SYSTEM_LANGUAGE) {
1202 locale = QLocale::system();
1203 localeLanguage = locale.name();
1204 } else {
1205 localeLanguage = newLanguage;
1206 locale = QLocale(localeLanguage);
1209 QLocale::setDefault(locale);
1210 switchTranslator(mainApp->translator,
1211 QStringLiteral("wireshark_%1.qm").arg(localeLanguage), QStringLiteral(":/i18n/"));
1212 if (QFile::exists(QStringLiteral("%1/%2/wireshark_%3.qm")
1213 .arg(get_datafile_dir()).arg("languages").arg(localeLanguage)))
1214 switchTranslator(mainApp->translator,
1215 QStringLiteral("wireshark_%1.qm").arg(localeLanguage), QString(get_datafile_dir()) + QStringLiteral("/languages"));
1216 if (QFile::exists(QStringLiteral("%1/wireshark_%3.qm")
1217 .arg(gchar_free_to_qstring(get_persconffile_path("languages", false))).arg(localeLanguage)))
1218 switchTranslator(mainApp->translator,
1219 QStringLiteral("wireshark_%1.qm").arg(localeLanguage), gchar_free_to_qstring(get_persconffile_path("languages", false)));
1220 if (QFile::exists(QStringLiteral("%1/qt_%2.qm")
1221 .arg(get_datafile_dir()).arg(localeLanguage))) {
1222 switchTranslator(mainApp->translatorQt,
1223 QStringLiteral("qt_%1.qm").arg(localeLanguage), QString(get_datafile_dir()));
1224 } else if (QFile::exists(QStringLiteral("%1/qt_%2.qm")
1225 .arg(get_datafile_dir()).arg(localeLanguage.left(localeLanguage.lastIndexOf('_'))))) {
1226 switchTranslator(mainApp->translatorQt,
1227 QStringLiteral("qt_%1.qm").arg(localeLanguage.left(localeLanguage.lastIndexOf('_'))), QString(get_datafile_dir()));
1228 } else {
1229 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
1230 QString translationPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath);
1231 #else
1232 QString translationPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath);
1233 #endif
1234 switchTranslator(mainApp->translatorQt, QStringLiteral("qt_%1.qm").arg(localeLanguage), translationPath);
1238 void MainApplication::doTriggerMenuItem(MainMenuItem menuItem)
1240 switch (menuItem)
1242 case FileOpenDialog:
1243 emit openCaptureFile(QString(), QString(), WTAP_TYPE_AUTO);
1244 break;
1245 case CaptureOptionsDialog:
1246 emit openCaptureOptions();
1247 break;
1251 void MainApplication::zoomTextFont(int zoomLevel)
1253 // Scale by 10%, rounding to nearest half point, minimum 1 point.
1254 // XXX Small sizes repeat. It might just be easier to create a map of multipliers.
1255 qreal zoom_size = mono_font_.pointSize() * 2 * qPow(qreal(1.1), zoomLevel);
1256 zoom_size = qRound(zoom_size) / qreal(2.0);
1257 zoom_size = qMax(zoom_size, qreal(1.0));
1259 zoomed_font_ = mono_font_;
1260 zoomed_font_.setPointSizeF(zoom_size);
1261 emit zoomMonospaceFont(zoomed_font_);
1263 QFont zoomed_application_font = font();
1264 zoomed_application_font.setPointSizeF(zoom_size);
1265 emit zoomRegularFont(zoomed_application_font);
1268 #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN)
1269 bool MainApplication::softwareUpdateCanShutdown() {
1270 software_update_ok_ = true;
1271 // At this point the update is ready to install, but WinSparkle has
1272 // not yet run the installer. We need to close our "Wireshark is
1273 // running" mutexes since the IsWiresharkRunning NSIS macro checks
1274 // for them.
1276 // We must not exit the Qt main event loop here, which means we must
1277 // not close the main window.
1279 // Step 1: See if we have any open files.
1280 emit softwareUpdateRequested();
1281 if (software_update_ok_ == true) {
1283 // Step 2: Close the "running" mutexes.
1284 close_app_running_mutex();
1286 return software_update_ok_;
1289 void MainApplication::softwareUpdateShutdownRequest() {
1290 // At this point the installer has been launched. Neither Wireshark nor
1291 // its children should have any "Wireshark is running" mutexes open.
1292 // The main window should still be open as noted above in
1293 // softwareUpdateCanShutdown and it's safe to exit the Qt main
1294 // event loop.
1296 // Step 3: Quit.
1297 emit softwareUpdateQuit();
1299 #endif
1301 void MainApplication::captureEventHandler(CaptureEvent ev)
1303 switch(ev.captureContext())
1305 #ifdef HAVE_LIBPCAP
1306 case CaptureEvent::Update:
1307 case CaptureEvent::Fixed:
1308 switch (ev.eventType())
1310 case CaptureEvent::Prepared:
1311 iface_mon_enable(true);
1312 break;
1313 case CaptureEvent::Started:
1314 active_captures_++;
1315 emit captureActive(active_captures_);
1316 break;
1317 case CaptureEvent::Finished:
1318 active_captures_--;
1319 emit captureActive(active_captures_);
1320 if (refresh_interfaces_pending_ && !global_capture_opts.restart) {
1321 refreshLocalInterfaces();
1323 break;
1324 default:
1325 break;
1327 break;
1328 #endif
1329 case CaptureEvent::File:
1330 case CaptureEvent::Reload:
1331 case CaptureEvent::Rescan:
1332 switch (ev.eventType())
1334 case CaptureEvent::Started:
1335 QTimer::singleShot(TAP_UPDATE_DEFAULT_INTERVAL / 5, this, SLOT(updateTaps()));
1336 QTimer::singleShot(TAP_UPDATE_DEFAULT_INTERVAL / 2, this, SLOT(updateTaps()));
1337 break;
1338 case CaptureEvent::Finished:
1339 updateTaps();
1340 break;
1341 default:
1342 break;
1344 break;
1345 default:
1346 break;
1350 void MainApplication::pushStatus(StatusInfo status, const QString &message, const QString &messagetip)
1352 if (! mainWindow() || ! qobject_cast<MainWindow *>(mainWindow()))
1353 return;
1355 MainWindow * mw = qobject_cast<MainWindow *>(mainWindow());
1356 if (! mw->statusBar())
1357 return;
1359 MainStatusBar * bar = mw->statusBar();
1361 switch(status)
1363 case FilterSyntax:
1364 bar->pushGenericStatus(MainStatusBar::STATUS_CTX_FILTER, message);
1365 break;
1366 case FieldStatus:
1367 bar->pushGenericStatus(MainStatusBar::STATUS_CTX_FIELD, message);
1368 break;
1369 case FileStatus:
1370 bar->pushGenericStatus(MainStatusBar::STATUS_CTX_FILE, message, messagetip);
1371 break;
1372 case ByteStatus:
1373 bar->pushGenericStatus(MainStatusBar::STATUS_CTX_BYTE, message);
1374 break;
1375 case BusyStatus:
1376 bar->pushGenericStatus(MainStatusBar::STATUS_CTX_PROGRESS, message, messagetip);
1377 break;
1378 case TemporaryStatus:
1379 bar->pushGenericStatus(MainStatusBar::STATUS_CTX_TEMPORARY, message);
1380 break;
1384 void MainApplication::popStatus(StatusInfo status)
1386 if (! mainWindow() || ! qobject_cast<MainWindow *>(mainWindow()))
1387 return;
1389 MainWindow * mw = qobject_cast<MainWindow *>(mainWindow());
1390 if (! mw->statusBar())
1391 return;
1393 MainStatusBar * bar = mw->statusBar();
1395 switch(status)
1397 case FilterSyntax:
1398 bar->popGenericStatus(MainStatusBar::STATUS_CTX_FILTER);
1399 break;
1400 case FieldStatus:
1401 bar->popGenericStatus(MainStatusBar::STATUS_CTX_FIELD);
1402 break;
1403 case FileStatus:
1404 bar->popGenericStatus(MainStatusBar::STATUS_CTX_FILE);
1405 break;
1406 case ByteStatus:
1407 bar->popGenericStatus(MainStatusBar::STATUS_CTX_BYTE);
1408 break;
1409 case BusyStatus:
1410 bar->popGenericStatus(MainStatusBar::STATUS_CTX_PROGRESS);
1411 break;
1412 case TemporaryStatus:
1413 bar->popGenericStatus(MainStatusBar::STATUS_CTX_TEMPORARY);
1414 break;
1418 void MainApplication::gotoFrame(int frame)
1420 if (! mainWindow() || ! qobject_cast<MainWindow *>(mainWindow()))
1421 return;
1423 MainWindow * mw = qobject_cast<MainWindow *>(mainWindow());
1424 mw->gotoFrame(frame);
1427 void MainApplication::reloadDisplayFilterMacros()
1429 dfilter_macro_reload();
1430 // The signal is needed when the display filter grammar changes for
1431 // any reason (not just "fields".)
1432 mainApp->emitAppSignal(MainApplication::FieldsChanged);