/* main_application.cpp * * Wireshark - Network traffic analyzer * By Gerald Combs * Copyright 1998 Gerald Combs * * SPDX-License-Identifier: GPL-2.0-or-later */ // warning C4267: 'argument' : conversion from 'size_t' to 'int', possible loss of data #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4267) #endif #define WS_LOG_DOMAIN LOG_DOMAIN_MAIN #include "main_application.h" #include #include #include "wsutil/filesystem.h" #include "epan/addr_resolv.h" #include "epan/column-utils.h" #include "epan/disabled_protos.h" #include "epan/ftypes/ftypes.h" #include "epan/prefs.h" #include "epan/proto.h" #include "epan/tap.h" #include "epan/timestamp.h" #include "epan/decode_as.h" #include "ui/decode_as_utils.h" #include "ui/preference_utils.h" #include "ui/iface_lists.h" #include "ui/language.h" #include "ui/recent.h" #include "ui/simple_dialog.h" #include "ui/util.h" #include #include #include "coloring_rules_dialog.h" #include "epan/color_filters.h" #include "recent_file_status.h" #include "extcap.h" #ifdef HAVE_LIBPCAP #include #endif #include "wsutil/filter_files.h" #include "ui/capture_globals.h" #include "ui/software_update.h" #include "ui/file_dialog.h" #include "ui/recent_utils.h" #ifdef HAVE_LIBPCAP #include "ui/capture.h" #endif #include "wsutil/utf8_entities.h" #ifdef _WIN32 # include "wsutil/file_util.h" # include # include #endif /* _WIN32 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) #include #endif #include #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) #include #endif #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && defined(Q_OS_WIN) #include #endif #ifdef _MSC_VER #pragma warning(pop) #endif MainApplication *mainApp = NULL; // XXX - Copied from ui/gtk/file_dlg.c static QList recent_captures_; static QHash > dynamic_menu_groups_; static QHash > added_menu_groups_; static QHash > removed_menu_groups_; QString MainApplication::window_title_separator_ = QString::fromUtf8(" " UTF8_MIDDLE_DOT " "); // QMimeDatabase parses a large-ish XML file and can be slow to initialize. // Do so in a worker thread as early as possible. // https://github.com/lxde/pcmanfm-qt/issues/415 class MimeDatabaseInitThread : public QRunnable { private: void run() { QMimeDatabase mime_db; mime_db.mimeTypeForData(QByteArray()); } }; #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) // Populating the font database can be slow as well. class FontDatabaseInitThread : public QRunnable { private: void run() { QFontDatabase font_db; } }; #endif void topic_action(topic_action_e action) { if (mainApp) mainApp->helpTopicAction(action); } /* * Add the capture filename to the application-wide "Recent Files" list. * Contrary to the name this isn't limited to the "recent" menu. */ /* * XXX - We might want to call SHAddToRecentDocs under Windows 7: * https://stackoverflow.com/questions/437212/how-do-you-register-a-most-recently-used-list-with-windows-in-preparation-for-win */ extern "C" void add_menu_recent_capture_file(const gchar *cf_name) { QString normalized_cf_name = QString::fromUtf8(cf_name); QDir cf_path; cf_path.setPath(normalized_cf_name); normalized_cf_name = cf_path.absolutePath(); normalized_cf_name = QDir::cleanPath(normalized_cf_name); normalized_cf_name = QDir::toNativeSeparators(normalized_cf_name); /* Iterate through the recent items list, removing duplicate entries and every * item above count_max */ unsigned int cnt = 1; QMutableListIterator rii(recent_captures_); while (rii.hasNext()) { recent_item_status *ri = rii.next(); /* if this element string is one of our special items (separator, ...) or * already in the list or * this element is above maximum count (too old), remove it */ if (ri->filename.length() < 1 || #ifdef _WIN32 /* do a case insensitive compare on win32 */ ri->filename.compare(normalized_cf_name, Qt::CaseInsensitive) == 0 || #else /* _WIN32 */ /* * Do a case sensitive compare on UN*Xes. * * XXX - on UN*Xes such as macOS, where you can use pathconf() * to check whether a given file system is case-sensitive or * not, we should check whether this particular file system * is case-sensitive and do the appropriate comparison. */ ri->filename.compare(normalized_cf_name) == 0 || #endif cnt >= prefs.gui_recent_files_count_max) { rii.remove(); delete(ri); cnt--; } cnt++; } mainApp->addRecentItem(normalized_cf_name, 0, false); } /* write all capture filenames of the menu to the user's recent file */ extern "C" void menu_recent_file_write_all(FILE *rf) { /* we have to iterate backwards through the children's list, * so we get the latest item last in the file. */ QListIterator rii(recent_captures_); rii.toBack(); while (rii.hasPrevious()) { QString cf_name; /* get capture filename from the menu item label */ cf_name = rii.previous()->filename; if (!cf_name.isNull()) { fprintf (rf, RECENT_KEY_CAPTURE_FILE ": %s\n", qUtf8Printable(cf_name)); } } } #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN) /** Check to see if Wireshark can shut down safely (e.g. offer to save the * current capture). */ extern "C" int software_update_can_shutdown_callback(void) { return mainApp->softwareUpdateCanShutdown(); } /** Shut down Wireshark in preparation for an upgrade. */ extern "C" void software_update_shutdown_request_callback(void) { mainApp->softwareUpdateShutdownRequest(); } #endif // HAVE_SOFTWARE_UPDATE && Q_OS_WIN // Check each recent item in a separate thread so that we don't hang while // calling stat(). This is called periodically because files and entire // volumes can disappear and reappear at any time. void MainApplication::refreshRecentCaptures() { recent_item_status *ri; RecentFileStatus *rf_status; // We're in the middle of a capture. Don't create traffic. if (active_captures_ > 0) return; foreach (ri, recent_captures_) { if (ri->in_thread) { continue; } rf_status = new RecentFileStatus(ri->filename, this); QThreadPool::globalInstance()->start(rf_status); } } void MainApplication::refreshPacketData() { if (host_name_lookup_process()) { emit addressResolutionChanged(); } else if (col_data_changed()) { emit columnDataChanged(); } } #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && defined(Q_OS_WIN) void MainApplication::colorSchemeChanged() { if (ColorUtils::themeIsDark()) { setStyle(QStyleFactory::create("fusion")); } else { setStyle(QStyleFactory::create("windowsvista")); } } #endif void MainApplication::updateTaps() { draw_tap_listeners(FALSE); } QDir MainApplication::openDialogInitialDir() { return QDir(get_open_dialog_initial_dir()); } void MainApplication::setLastOpenDirFromFilename(const QString file_name) { /* XXX - Use canonicalPath() instead of absolutePath()? */ QString directory = QDir::toNativeSeparators(QFileInfo(file_name).absolutePath()); /* XXX - printable? */ set_last_open_dir(qUtf8Printable(directory)); } void MainApplication::helpTopicAction(topic_action_e action) { QString url = gchar_free_to_qstring(topic_action_url(action)); if (!url.isEmpty()) { QDesktopServices::openUrl(QUrl(url)); } } const QFont MainApplication::monospaceFont(bool zoomed) const { if (zoomed) { return zoomed_font_; } return mono_font_; } void MainApplication::setMonospaceFont(const char *font_string) { if (font_string && strlen(font_string) > 0) { #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) // Qt 6's QFont::toString returns a value with 16 or 17 fields, e.g. // Consolas,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1 // Corbel,10,-1,5,400,0,0,0,0,0,0,0,0,0,0,1,Regular // Qt 5's QFont::fromString expects a value with 10 or 11 fields, e.g. // Consolas,10,-1,5,50,0,0,0,0,0 // Corbel,10,-1,5,50,0,0,0,0,0,Regular // It looks like Qt6's QFont::fromString can read both forms: // https://github.com/qt/qtbase/blob/6.0/src/gui/text/qfont.cpp#L2146 // but Qt5's cannot: // https://github.com/qt/qtbase/blob/5.15/src/gui/text/qfont.cpp#L2151 const char *fs_ptr = font_string; int field_count = 1; while ((fs_ptr = strchr(fs_ptr, ',')) != NULL) { fs_ptr++; field_count++; } if (field_count <= 11) { #endif mono_font_.fromString(font_string); // Only accept the font name if it actually exists. if (mono_font_.family() == QFontInfo(mono_font_).family()) { return; } else { ws_warning("Monospace font family %s differs from its fontinfo: %s", qUtf8Printable(mono_font_.family()), qUtf8Printable(QFontInfo(mono_font_).family())); } #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) } else { ws_warning("Monospace font %s appears to be from Qt6 and we're running Qt5.", font_string); } #endif } // https://en.wikipedia.org/wiki/Category:Monospaced_typefaces const char *win_default_font = "Consolas"; const char *win_alt_font = "Lucida Console"; // SF Mono might be a system font someday. Right now (Oct 2016) it appears // to be limited to Xcode and Terminal. // http://www.openradar.me/26790072 // http://www.openradar.me/26862220 const char *osx_default_font = "SF Mono"; const QStringList osx_alt_fonts = QStringList() << "Menlo" << "Monaco"; // XXX Detect Ubuntu systems (e.g. via /etc/os-release and/or // /etc/lsb_release) and add "Ubuntu Mono Regular" there. // https://design.ubuntu.com/font/ const char *x11_default_font = "Liberation Mono"; const QStringList x11_alt_fonts = QStringList() << "DejaVu Sans Mono" << "Bitstream Vera Sans Mono"; const QStringList fallback_fonts = QStringList() << "Lucida Sans Typewriter" << "Inconsolata" << "Droid Sans Mono" << "Andale Mono" << "Courier New" << "monospace"; QStringList substitutes; int font_size_adjust = 0; // Try to pick the latest, shiniest fixed-width font for our OS. #if defined(Q_OS_WIN) const char *default_font = win_default_font; substitutes << win_alt_font << osx_default_font << osx_alt_fonts << x11_default_font << x11_alt_fonts << fallback_fonts; # if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) font_size_adjust = 1; # else // QT_VERSION font_size_adjust = 2; # endif // QT_VERSION #elif defined(Q_OS_MAC) const char *default_font = osx_default_font; substitutes << osx_alt_fonts << win_default_font << win_alt_font << x11_default_font << x11_alt_fonts << fallback_fonts; #else // Q_OS const char *default_font = x11_default_font; substitutes << x11_alt_fonts << win_default_font << win_alt_font << osx_default_font << osx_alt_fonts << fallback_fonts; #endif // Q_OS mono_font_ = QFont(default_font, mainApp->font().pointSize() + font_size_adjust); mono_font_.insertSubstitutions(default_font, substitutes); mono_font_.setBold(false); // Retrieve the effective font and apply it. mono_font_.setFamily(QFontInfo(mono_font_).family()); g_free(prefs.gui_font_name); prefs.gui_font_name = qstring_strdup(mono_font_.toString()); } int MainApplication::monospaceTextSize(const char *str) { #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)) return QFontMetrics(mono_font_).horizontalAdvance(str); #else return QFontMetrics(mono_font_).width(str); #endif } void MainApplication::setConfigurationProfile(const gchar *profile_name, bool write_recent_file) { char *rf_path; int rf_open_errno; gchar *err_msg = NULL; gboolean prev_capture_no_interface_load; gboolean prev_capture_no_extcap; /* First check if profile exists */ if (!profile_exists(profile_name, FALSE)) { if (profile_exists(profile_name, TRUE)) { char *pf_dir_path, *pf_dir_path2, *pf_filename; /* Copy from global profile */ if (create_persconffile_profile(profile_name, &pf_dir_path) == -1) { simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "Can't create directory\n\"%s\":\n%s.", pf_dir_path, g_strerror(errno)); g_free(pf_dir_path); } if (copy_persconffile_profile(profile_name, profile_name, TRUE, &pf_filename, &pf_dir_path, &pf_dir_path2) == -1) { simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "Can't copy file \"%s\" in directory\n\"%s\" to\n\"%s\":\n%s.", pf_filename, pf_dir_path2, pf_dir_path, g_strerror(errno)); g_free(pf_filename); g_free(pf_dir_path); g_free(pf_dir_path2); } } else { /* No personal and no global profile exists */ return; } } /* Then check if changing to another profile */ if (profile_name && strcmp (profile_name, get_profile_name()) == 0) { return; } prev_capture_no_interface_load = prefs.capture_no_interface_load; prev_capture_no_extcap = prefs.capture_no_extcap; /* Get the current geometry, before writing it to disk */ emit profileChanging(); if (write_recent_file && profile_exists(get_profile_name(), FALSE)) { /* Write recent file for profile we are leaving, if it still exists */ write_profile_recent(); } /* Set profile name and update the status bar */ set_profile_name (profile_name); emit profileNameChanged(profile_name); /* Apply new preferences */ readConfigurationFiles(true); if (!recent_read_profile_static(&rf_path, &rf_open_errno)) { simple_dialog(ESD_TYPE_WARN, ESD_BTN_OK, "Could not open common recent file\n\"%s\": %s.", rf_path, g_strerror(rf_open_errno)); g_free(rf_path); } if (recent.gui_fileopen_remembered_dir && test_for_directory(recent.gui_fileopen_remembered_dir) == EISDIR) { set_last_open_dir(recent.gui_fileopen_remembered_dir); } timestamp_set_type(recent.gui_time_format); timestamp_set_precision(recent.gui_time_precision); timestamp_set_seconds_type (recent.gui_seconds_format); tap_update_timer_.setInterval(prefs.tap_update_interval); prefs_to_capture_opts(); prefs_apply_all(); #ifdef HAVE_LIBPCAP update_local_interfaces(); #endif setMonospaceFont(prefs.gui_font_name); // Freeze the packet list early to avoid updating column data before doing a // full redissection. The packet list will be thawed when redissection is done. emit freezePacketList(true); emit columnsChanged(); emit preferencesChanged(); emit recentPreferencesRead(); emit filterExpressionsChanged(); emit checkDisplayFilter(); emit captureFilterListChanged(); emit displayFilterListChanged(); /* Reload color filters */ if (!color_filters_reload(&err_msg, color_filter_add_cb)) { simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err_msg); g_free(err_msg); } /* Load interfaces if settings have changed */ if (!prefs.capture_no_interface_load && ((prefs.capture_no_interface_load != prev_capture_no_interface_load) || (prefs.capture_no_extcap != prev_capture_no_extcap))) { refreshLocalInterfaces(); } emit localInterfaceListChanged(); emit packetDissectionChanged(); /* Write recent_common file to ensure last used profile setting is stored. */ write_recent(); } void MainApplication::reloadLuaPluginsDelayed() { QTimer::singleShot(0, this, SIGNAL(reloadLuaPlugins())); } const QIcon &MainApplication::normalIcon() { if (normal_icon_.isNull()) { initializeIcons(); } return normal_icon_; } const QIcon &MainApplication::captureIcon() { if (capture_icon_.isNull()) { initializeIcons(); } return capture_icon_; } const QString MainApplication::windowTitleString(QStringList title_parts) { QMutableStringListIterator tii(title_parts); while (tii.hasNext()) { QString ti = tii.next(); if (ti.isEmpty()) tii.remove(); } title_parts.prepend(applicationName()); return title_parts.join(window_title_separator_); } void MainApplication::applyCustomColorsFromRecent() { int i = 0; bool ok; for (GList *custom_color = recent.custom_colors; custom_color; custom_color = custom_color->next) { QRgb rgb = QString((const char *)custom_color->data).toUInt(&ok, 16); if (ok) { QColorDialog::setCustomColor(i++, QColor(rgb)); } } } // Return the first top-level QMainWindow. QWidget *MainApplication::mainWindow() { foreach (QWidget *tlw, topLevelWidgets()) { QMainWindow *tlmw = qobject_cast(tlw); if (tlmw && tlmw->isVisible()) { return tlmw; } } return 0; } void MainApplication::storeCustomColorsInRecent() { if (QColorDialog::customCount()) { prefs_clear_string_list(recent.custom_colors); recent.custom_colors = NULL; for (int i = 0; i < QColorDialog::customCount(); i++) { QRgb rgb = QColorDialog::customColor(i).rgb(); recent.custom_colors = g_list_append(recent.custom_colors, ws_strdup_printf("%08x", rgb)); } } } bool MainApplication::event(QEvent *event) { QString display_filter = NULL; if (event->type() == QEvent::FileOpen) { QFileOpenEvent *foe = static_cast(event); if (foe && foe->file().length() > 0) { QString cf_path(foe->file()); if (initialized_) { emit openCaptureFile(cf_path, display_filter, WTAP_TYPE_AUTO); } else { pending_open_files_.append(cf_path); } } return true; } return QApplication::event(event); } void MainApplication::clearRecentCaptures() { qDeleteAll(recent_captures_); recent_captures_.clear(); emit updateRecentCaptureStatus(NULL, 0, false); } void MainApplication::cleanup() { software_update_cleanup(); storeCustomColorsInRecent(); // Write the user's recent file(s) to disk. write_profile_recent(); write_recent(); qDeleteAll(recent_captures_); recent_captures_.clear(); // We might end up here via exit_application. QThreadPool::globalInstance()->waitForDone(); } void MainApplication::itemStatusFinished(const QString filename, qint64 size, bool accessible) { recent_item_status *ri; foreach (ri, recent_captures_) { if (filename == ri->filename && (size != ri->size || accessible != ri->accessible)) { ri->size = size; ri->accessible = accessible; ri->in_thread = false; emit updateRecentCaptureStatus(filename, size, accessible); } } } MainApplication::MainApplication(int &argc, char **argv) : QApplication(argc, argv), initialized_(false), is_reloading_lua_(false), if_notifier_(NULL), active_captures_(0) { mainApp = this; MimeDatabaseInitThread *mime_db_init_thread = new(MimeDatabaseInitThread); QThreadPool::globalInstance()->start(mime_db_init_thread); #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) FontDatabaseInitThread *font_db_init_thread = new (FontDatabaseInitThread); QThreadPool::globalInstance()->start(font_db_init_thread); #endif Q_INIT_RESOURCE(about); Q_INIT_RESOURCE(i18n); Q_INIT_RESOURCE(layout); Q_INIT_RESOURCE(stock_icons); Q_INIT_RESOURCE(languages); #ifdef Q_OS_WIN /* RichEd20.DLL is needed for native file dialog filter entries. */ ws_load_library("riched20.dll"); #endif // Q_OS_WIN #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) setAttribute(Qt::AA_UseHighDpiPixmaps); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) && defined(Q_OS_WIN) setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) setAttribute(Qt::AA_DisableWindowContextHelpButton); #endif // Throw various settings at the wall with the hope that one of them will // enable context menu shortcuts QTBUG-69452, QTBUG-109590 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) setAttribute(Qt::AA_DontShowShortcutsInContextMenus, false); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) styleHints()->setShowShortcutsInContextMenus(true); #endif // // XXX - this means we try to check for the existence of all files // in the recent list every 2 seconds; that causes noticeable network // traffic if any of them are stored on file servers. // // QFileSystemWatcher should allow us to watch for files being // removed or renamed. It uses kqueues and EVFILT_VNODE on FreeBSD, // NetBSD, FSEvents on macOS, inotify on Linux if available, and // FindFirstChagneNotification() on Windows. On all other platforms, // it just periodically polls, as we're doing now. // // For unmounts: // // macOS and FreeBSD deliver NOTE_REVOKE notes for EVFILT_VNODE, and // QFileSystemWatcher delivers signals for them, just as it does for // NOTE_DELETE and NOTE_RENAME. // // On Linux, inotify: // // http://man7.org/linux/man-pages/man7/inotify.7.html // // appears to deliver "filesystem containing watched object was // unmounted" events. It looks as if Qt turns them into "changed" // events. // // On Windows, it's not clearly documented what happens on a handle // opened with FindFirstChangeNotification() if the volume on which // the path handed to FindFirstChangeNotification() is removed, or // ejected, or whatever the Windowsese is for "unmounted". The // handle obviously isn't valid any more, but whether it just hangs // around and never delivers any notifications or delivers an // event that turns into an error indication doesn't seem to be // documented. If it just hangs around, I think our main loop will // receive a WM_DEVICECHANGE Windows message with DBT_DEVICEREMOVECOMPLETE // if an unmount occurs - even for network devices. If we need to watch // for those, we can use the winEvent method of the QWidget for the // top-level window to get Windows messages. // // Note also that remote file systems might not report file // removal or renames if they're done on the server or done by // another client. At least on macOS, they *will* get reported // if they're done on the machine running the program doing the // kqueue stuff, and, at least in newer versions, should get // reported on SMB-mounted (and AFP-mounted?) file systems // even if done on the server or another client. // // But, when push comes to shove, the file manager(s) on the // OSes in question probably use the same mechanisms to // monitor folders in folder windows or open/save dialogs or..., // so my inclination is just to use QFileSystemWatcher. // // However, that wouldn't catch files that become *re*-accessible // by virtue of a file system being re-mounted. The only way to // catch *that* would be to watch for mounts and re-check all // marked-as-inaccessible files. // // macOS and FreeBSD also support EVFILT_FS events, which notify you // of file system mounts and unmounts. We'd need to add our own // kqueue for that, if we can check those with QSocketNotifier. // // On Linux, at least as of 2006, you're supposed to poll /proc/mounts: // // https://lkml.org/lkml/2006/2/22/169 // // to discover mounts. // // On Windows, you'd probably have to watch for WM_DEVICECHANGE events. // // Then again, with an automounter, a file system containing a // recent capture might get unmounted automatically if you haven't // referred to anything on that file system for a while, and get // treated as inaccessible. However, if you try to access it, // the automounter will attempt to re-mount it, so the access *will* // succeed if the automounter can remount the file. // // (Speaking of automounters, repeatedly polling recent files will // keep the file system from being unmounted, for what that's worth.) // // At least on macOS, you can determine whether a file is on an // automounted file system by calling statfs() on its path and // checking whether MNT_AUTOMOUNTED is set in f_flags. FreeBSD // appears to support that flag as well, but no other *BSD appears // to. // // I'm not sure what can be done on Linux. // recent_timer_.setParent(this); connect(&recent_timer_, SIGNAL(timeout()), this, SLOT(refreshRecentCaptures())); recent_timer_.start(2000); packet_data_timer_.setParent(this); connect(&packet_data_timer_, SIGNAL(timeout()), this, SLOT(refreshPacketData())); packet_data_timer_.start(1000); tap_update_timer_.setParent(this); tap_update_timer_.setInterval(TAP_UPDATE_DEFAULT_INTERVAL); connect(this, SIGNAL(appInitialized()), &tap_update_timer_, SLOT(start())); connect(&tap_update_timer_, SIGNAL(timeout()), this, SLOT(updateTaps())); // Application-wide style sheet QString app_style_sheet = qApp->styleSheet(); qApp->setStyleSheet(app_style_sheet); // If our window text is lighter than the window background, assume the theme is dark. prefs_set_gui_theme_is_dark(ColorUtils::themeIsDark()); #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN) connect(this, SIGNAL(softwareUpdateQuit()), this, SLOT(quit()), Qt::QueuedConnection); #endif #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) && defined(Q_OS_WIN) colorSchemeChanged(); connect(styleHints(), &QStyleHints::colorSchemeChanged, this, &MainApplication::colorSchemeChanged); #endif connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(cleanup())); } MainApplication::~MainApplication() { mainApp = NULL; clearDynamicMenuGroupItems(); free_filter_lists(); } void MainApplication::registerUpdate(register_action_e action, const char *message) { emit splashUpdate(action, message); } void MainApplication::emitAppSignal(AppSignal signal) { switch (signal) { case ColumnsChanged: emit columnsChanged(); break; case CaptureFilterListChanged: emit captureFilterListChanged(); break; case DisplayFilterListChanged: emit displayFilterListChanged(); break; case FilterExpressionsChanged: emit filterExpressionsChanged(); break; case LocalInterfacesChanged: emit localInterfaceListChanged(); break; case NameResolutionChanged: emit addressResolutionChanged(); break; case PreferencesChanged: emit preferencesChanged(); break; case PacketDissectionChanged: emit packetDissectionChanged(); break; case ProfileChanging: emit profileChanging(); break; case RecentCapturesChanged: emit updateRecentCaptureStatus(NULL, 0, false); break; case RecentPreferencesRead: emit recentPreferencesRead(); break; case FieldsChanged: emit fieldsChanged(); break; case FreezePacketList: emit freezePacketList(false); break; default: break; } } // Flush any collected app signals. // // On macOS emitting PacketDissectionChanged from a dialog can // render the application unusable: // https://gitlab.com/wireshark/wireshark/-/issues/11361 // https://gitlab.com/wireshark/wireshark/-/issues/11448 // Work around the problem by queueing up app signals and emitting them // after the dialog is closed. // // The following bugs might be related although they don't describe the // exact behavior we're working around here: // https://bugreports.qt.io/browse/QTBUG-38512 // https://bugreports.qt.io/browse/QTBUG-38600 void MainApplication::flushAppSignals() { while (!app_signals_.isEmpty()) { mainApp->emitAppSignal(app_signals_.takeFirst()); } } void MainApplication::emitStatCommandSignal(const QString &menu_path, const char *arg, void *userdata) { emit openStatCommandDialog(menu_path, arg, userdata); } void MainApplication::emitTapParameterSignal(const QString cfg_abbr, const QString arg, void *userdata) { emit openTapParameterDialog(cfg_abbr, arg, userdata); } // XXX Combine statistics and funnel routines into addGroupItem + groupItems? void MainApplication::addDynamicMenuGroupItem(int group, QAction *sg_action) { if (!dynamic_menu_groups_.contains(group)) { dynamic_menu_groups_[group] = QList(); } dynamic_menu_groups_[group] << sg_action; } void MainApplication::appendDynamicMenuGroupItem(int group, QAction *sg_action) { if (!added_menu_groups_.contains(group)) { added_menu_groups_[group] = QList(); } added_menu_groups_[group] << sg_action; addDynamicMenuGroupItem(group, sg_action); } void MainApplication::removeDynamicMenuGroupItem(int group, QAction *sg_action) { if (!removed_menu_groups_.contains(group)) { removed_menu_groups_[group] = QList(); } removed_menu_groups_[group] << sg_action; dynamic_menu_groups_[group].removeAll(sg_action); } void MainApplication::clearDynamicMenuGroupItems() { foreach (int group, dynamic_menu_groups_.keys()) { dynamic_menu_groups_[group].clear(); } } QList MainApplication::dynamicMenuGroupItems(int group) { if (!dynamic_menu_groups_.contains(group)) { return QList(); } QList sgi_list = dynamic_menu_groups_[group]; std::sort(sgi_list.begin(), sgi_list.end(), qActionLessThan); return sgi_list; } QList MainApplication::addedMenuGroupItems(int group) { if (!added_menu_groups_.contains(group)) { return QList(); } QList sgi_list = added_menu_groups_[group]; std::sort(sgi_list.begin(), sgi_list.end(), qActionLessThan); return sgi_list; } QList MainApplication::removedMenuGroupItems(int group) { if (!removed_menu_groups_.contains(group)) { return QList(); } QList sgi_list = removed_menu_groups_[group]; std::sort(sgi_list.begin(), sgi_list.end(), qActionLessThan); return sgi_list; } void MainApplication::clearAddedMenuGroupItems() { foreach (int group, added_menu_groups_.keys()) { added_menu_groups_[group].clear(); } } void MainApplication::clearRemovedMenuGroupItems() { foreach (int group, removed_menu_groups_.keys()) { foreach (QAction *action, removed_menu_groups_[group]) { delete action; } removed_menu_groups_[group].clear(); } } #ifdef HAVE_LIBPCAP static void iface_mon_event_cb(const char *iface, int added, int up) { int present = 0; guint ifs, j; interface_t *device; interface_options *interface_opts; for (ifs = 0; ifs < global_capture_opts.all_ifaces->len; ifs++) { device = &g_array_index(global_capture_opts.all_ifaces, interface_t, ifs); if (strcmp(device->name, iface) == 0) { present = 1; if (!up) { /* * Interface went down or disappeared; remove all instances * of it from the current list of interfaces selected * for capturing. */ for (j = 0; j < global_capture_opts.ifaces->len; j++) { interface_opts = &g_array_index(global_capture_opts.ifaces, interface_options, j); if (strcmp(interface_opts->name, device->name) == 0) { capture_opts_del_iface(&global_capture_opts, j); } } } } } mainApp->emitLocalInterfaceEvent(iface, added, up); if (present != up) { /* * We've been told that there's a new interface or that an old * interface is gone; reload the local interface list. */ mainApp->refreshLocalInterfaces(); } } #endif void MainApplication::ifChangeEventsAvailable() { #ifdef HAVE_LIBPCAP /* * Something's readable from the descriptor for interface * monitoring. * * Have the interface-monitoring code Read whatever interface-change * events are available, and call the callback for them. */ iface_mon_event(); #endif } void MainApplication::emitLocalInterfaceEvent(const char *ifname, int added, int up) { emit localInterfaceEvent(ifname, added, up); } void MainApplication::refreshLocalInterfaces() { extcap_clear_interfaces(); #ifdef HAVE_LIBPCAP /* * Reload the local interface list. */ scan_local_interfaces(main_window_update); /* * Now emit a signal to indicate that the list changed, so that all * places displaying the list will get updated. * * XXX - only if it *did* change. */ emit localInterfaceListChanged(); #endif } void MainApplication::allSystemsGo() { QString display_filter = NULL; initialized_ = true; emit appInitialized(); while (pending_open_files_.length() > 0) { emit openCaptureFile(pending_open_files_.front(), display_filter, WTAP_TYPE_AUTO); pending_open_files_.pop_front(); } software_update_init(); #ifdef HAVE_LIBPCAP int err; err = iface_mon_start(&iface_mon_event_cb); if (err == 0) { if_notifier_ = new QSocketNotifier(iface_mon_get_sock(), QSocketNotifier::Read, this); connect(if_notifier_, SIGNAL(activated(int)), SLOT(ifChangeEventsAvailable())); } #endif } _e_prefs *MainApplication::readConfigurationFiles(bool reset) { e_prefs *prefs_p; if (reset) { // // Reset current preferences and enabled/disabled protocols and // heuristic dissectors before reading. // (Needed except when this is called at startup.) // prefs_reset(); proto_reenable_all(); } /* Load libwireshark settings from the current profile. */ prefs_p = epan_load_settings(); /* Read the capture filter file. */ read_filter_list(CFILTER_LIST); return prefs_p; } QList MainApplication::recentItems() const { return recent_captures_; } void MainApplication::addRecentItem(const QString filename, qint64 size, bool accessible) { recent_item_status *ri = new(recent_item_status); ri->filename = filename; ri->size = size; ri->accessible = accessible; ri->in_thread = false; recent_captures_.prepend(ri); itemStatusFinished(filename, size, accessible); } void MainApplication::removeRecentItem(const QString &filename) { QMutableListIterator rii(recent_captures_); while (rii.hasNext()) { recent_item_status *ri = rii.next(); #ifdef _WIN32 /* Do a case insensitive compare on win32 */ if (ri->filename.compare(filename, Qt::CaseInsensitive) == 0) { #else /* Do a case sensitive compare on UN*Xes. * * XXX - on UN*Xes such as macOS, where you can use pathconf() * to check whether a given file system is case-sensitive or * not, we should check whether this particular file system * is case-sensitive and do the appropriate comparison. */ if (ri->filename.compare(filename) == 0) { #endif rii.remove(); delete(ri); } } emit updateRecentCaptureStatus(NULL, 0, false); } static void switchTranslator(QTranslator& myTranslator, const QString& filename, const QString& searchPath) { mainApp->removeTranslator(&myTranslator); if (myTranslator.load(filename, searchPath)) mainApp->installTranslator(&myTranslator); } void MainApplication::loadLanguage(const QString newLanguage) { QLocale locale; QString localeLanguage; if (newLanguage.isEmpty() || newLanguage == USE_SYSTEM_LANGUAGE) { locale = QLocale::system(); localeLanguage = locale.name(); } else { localeLanguage = newLanguage; locale = QLocale(localeLanguage); } QLocale::setDefault(locale); switchTranslator(mainApp->translator, QString("wireshark_%1.qm").arg(localeLanguage), QString(":/i18n/")); if (QFile::exists(QString("%1/%2/wireshark_%3.qm") .arg(get_datafile_dir()).arg("languages").arg(localeLanguage))) switchTranslator(mainApp->translator, QString("wireshark_%1.qm").arg(localeLanguage), QString(get_datafile_dir()) + QString("/languages")); if (QFile::exists(QString("%1/wireshark_%3.qm") .arg(gchar_free_to_qstring(get_persconffile_path("languages", FALSE))).arg(localeLanguage))) switchTranslator(mainApp->translator, QString("wireshark_%1.qm").arg(localeLanguage), gchar_free_to_qstring(get_persconffile_path("languages", FALSE))); if (QFile::exists(QString("%1/qt_%2.qm") .arg(get_datafile_dir()).arg(localeLanguage))) { switchTranslator(mainApp->translatorQt, QString("qt_%1.qm").arg(localeLanguage), QString(get_datafile_dir())); } else if (QFile::exists(QString("%1/qt_%2.qm") .arg(get_datafile_dir()).arg(localeLanguage.left(localeLanguage.lastIndexOf('_'))))) { switchTranslator(mainApp->translatorQt, QString("qt_%1.qm").arg(localeLanguage.left(localeLanguage.lastIndexOf('_'))), QString(get_datafile_dir())); } else { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QString translationPath = QLibraryInfo::path(QLibraryInfo::TranslationsPath); #else QString translationPath = QLibraryInfo::location(QLibraryInfo::TranslationsPath); #endif switchTranslator(mainApp->translatorQt, QString("qt_%1.qm").arg(localeLanguage), translationPath); } } void MainApplication::doTriggerMenuItem(MainMenuItem menuItem) { switch (menuItem) { case FileOpenDialog: emit openCaptureFile(QString(), QString(), WTAP_TYPE_AUTO); break; case CaptureOptionsDialog: emit openCaptureOptions(); break; } } void MainApplication::zoomTextFont(int zoomLevel) { // Scale by 10%, rounding to nearest half point, minimum 1 point. // XXX Small sizes repeat. It might just be easier to create a map of multipliers. qreal zoom_size = mono_font_.pointSize() * 2 * qPow(qreal(1.1), zoomLevel); zoom_size = qRound(zoom_size) / qreal(2.0); zoom_size = qMax(zoom_size, qreal(1.0)); zoomed_font_ = mono_font_; zoomed_font_.setPointSizeF(zoom_size); emit zoomMonospaceFont(zoomed_font_); QFont zoomed_application_font = font(); zoomed_application_font.setPointSizeF(zoom_size); emit zoomRegularFont(zoomed_application_font); } #if defined(HAVE_SOFTWARE_UPDATE) && defined(Q_OS_WIN) bool MainApplication::softwareUpdateCanShutdown() { software_update_ok_ = true; // At this point the update is ready to install, but WinSparkle has // not yet run the installer. We need to close our "Wireshark is // running" mutexes since the IsWiresharkRunning NSIS macro checks // for them. // // We must not exit the Qt main event loop here, which means we must // not close the main window. // Step 1: See if we have any open files. emit softwareUpdateRequested(); if (software_update_ok_ == true) { // Step 2: Close the "running" mutexes. close_app_running_mutex(); } return software_update_ok_; } void MainApplication::softwareUpdateShutdownRequest() { // At this point the installer has been launched. Neither Wireshark nor // its children should have any "Wireshark is running" mutexes open. // The main window should still be open as noted above in // softwareUpdateCanShutdown and it's safe to exit the Qt main // event loop. // Step 3: Quit. emit softwareUpdateQuit(); } #endif void MainApplication::captureEventHandler(CaptureEvent ev) { switch(ev.captureContext()) { #ifdef HAVE_LIBPCAP case CaptureEvent::Update: case CaptureEvent::Fixed: switch (ev.eventType()) { case CaptureEvent::Started: active_captures_++; emit captureActive(active_captures_); break; case CaptureEvent::Finished: active_captures_--; emit captureActive(active_captures_); break; default: break; } break; #endif case CaptureEvent::File: case CaptureEvent::Reload: case CaptureEvent::Rescan: switch (ev.eventType()) { case CaptureEvent::Started: QTimer::singleShot(TAP_UPDATE_DEFAULT_INTERVAL / 5, this, SLOT(updateTaps())); QTimer::singleShot(TAP_UPDATE_DEFAULT_INTERVAL / 2, this, SLOT(updateTaps())); break; case CaptureEvent::Finished: updateTaps(); break; default: break; } break; default: break; } } void MainApplication::pushStatus(StatusInfo status, const QString &message, const QString &messagetip) { if (! mainWindow() || ! qobject_cast(mainWindow())) return; MainWindow * mw = qobject_cast(mainWindow()); if (! mw->statusBar()) return; MainStatusBar * bar = mw->statusBar(); switch(status) { case FilterSyntax: bar->pushGenericStatus(MainStatusBar::STATUS_CTX_FILTER, message); break; case FieldStatus: bar->pushGenericStatus(MainStatusBar::STATUS_CTX_FIELD, message); break; case FileStatus: bar->pushGenericStatus(MainStatusBar::STATUS_CTX_FILE, message, messagetip); break; case ByteStatus: bar->pushGenericStatus(MainStatusBar::STATUS_CTX_BYTE, message); break; case BusyStatus: bar->pushGenericStatus(MainStatusBar::STATUS_CTX_PROGRESS, message, messagetip); break; case TemporaryStatus: bar->pushGenericStatus(MainStatusBar::STATUS_CTX_TEMPORARY, message); break; } } void MainApplication::popStatus(StatusInfo status) { if (! mainWindow() || ! qobject_cast(mainWindow())) return; MainWindow * mw = qobject_cast(mainWindow()); if (! mw->statusBar()) return; MainStatusBar * bar = mw->statusBar(); switch(status) { case FilterSyntax: bar->popGenericStatus(MainStatusBar::STATUS_CTX_FILTER); break; case FieldStatus: bar->popGenericStatus(MainStatusBar::STATUS_CTX_FIELD); break; case FileStatus: bar->popGenericStatus(MainStatusBar::STATUS_CTX_FILE); break; case ByteStatus: bar->popGenericStatus(MainStatusBar::STATUS_CTX_BYTE); break; case BusyStatus: bar->popGenericStatus(MainStatusBar::STATUS_CTX_PROGRESS); break; case TemporaryStatus: bar->popGenericStatus(MainStatusBar::STATUS_CTX_TEMPORARY); break; } } void MainApplication::gotoFrame(int frame) { if (! mainWindow() || ! qobject_cast(mainWindow())) return; MainWindow * mw = qobject_cast(mainWindow()); mw->gotoFrame(frame); }