From e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 10 Apr 2024 22:34:10 +0200 Subject: Adding upstream version 4.2.2. Signed-off-by: Daniel Baumann --- ui/qt/main_application.cpp | 1383 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1383 insertions(+) create mode 100644 ui/qt/main_application.cpp (limited to 'ui/qt/main_application.cpp') diff --git a/ui/qt/main_application.cpp b/ui/qt/main_application.cpp new file mode 100644 index 00000000..bc52b6cf --- /dev/null +++ b/ui/qt/main_application.cpp @@ -0,0 +1,1383 @@ +/* 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) +{ + QString directory = 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); +} -- cgit v1.2.3