diff --git a/ui/logray/CMakeLists.txt b/ui/logray/CMakeLists.txt index 377386738b..db9d6e494d 100644 --- a/ui/logray/CMakeLists.txt +++ b/ui/logray/CMakeLists.txt @@ -175,6 +175,7 @@ set(WIRESHARK_QT_HEADERS ../qt/funnel_string_dialog.h ../qt/funnel_text_dialog.h ../qt/geometry_state_dialog.h + ../qt/glib_mainloop_on_qeventloop.h ../qt/import_text_dialog.h ../qt/interface_frame.h ../qt/interface_toolbar_reader.h @@ -394,6 +395,7 @@ set(WIRESHARK_QT_SRC ../qt/funnel_string_dialog.cpp ../qt/funnel_text_dialog.cpp ../qt/geometry_state_dialog.cpp + ../qt/glib_mainloop_on_qeventloop.cpp ../qt/import_text_dialog.cpp ../qt/interface_frame.cpp ../qt/interface_toolbar_reader.cpp diff --git a/ui/logray/logray_main.cpp b/ui/logray/logray_main.cpp index 33ab7ed2a4..5288027ce0 100644 --- a/ui/logray/logray_main.cpp +++ b/ui/logray/logray_main.cpp @@ -81,6 +81,7 @@ #include "ui/qt/utils/color_utils.h" #include "ui/qt/coloring_rules_dialog.h" #include "ui/qt/endpoint_dialog.h" +#include "ui/qt/glib_mainloop_on_qeventloop.h" #include "ui/logray/logray_main_window.h" #include "ui/qt/simple_dialog.h" #include "ui/qt/simple_statistics_dialog.h" @@ -687,6 +688,8 @@ int main(int argc, char *qt_argv[]) // Init the main window (and splash) main_w = new(LograyMainWindow); main_w->show(); + // Setup GLib mainloop on Qt event loop to enable GLib and GIO watches + GLibMainloopOnQEventLoop::setup(main_w); // We may not need a queued connection here but it would seem to make sense // to force the issue. main_w->connect(&ls_app, SIGNAL(openCaptureFile(QString,QString,unsigned int)), diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt index e2a9f591bd..c13c08c14a 100644 --- a/ui/qt/CMakeLists.txt +++ b/ui/qt/CMakeLists.txt @@ -178,6 +178,7 @@ set(WIRESHARK_QT_HEADERS funnel_string_dialog.h funnel_text_dialog.h geometry_state_dialog.h + glib_mainloop_on_qeventloop.h gsm_map_summary_dialog.h iax2_analysis_dialog.h import_text_dialog.h @@ -422,6 +423,7 @@ set(WIRESHARK_QT_SRC funnel_string_dialog.cpp funnel_text_dialog.cpp geometry_state_dialog.cpp + glib_mainloop_on_qeventloop.cpp iax2_analysis_dialog.cpp import_text_dialog.cpp interface_frame.cpp diff --git a/ui/qt/glib_mainloop_on_qeventloop.cpp b/ui/qt/glib_mainloop_on_qeventloop.cpp new file mode 100644 index 0000000000..f715bbc728 --- /dev/null +++ b/ui/qt/glib_mainloop_on_qeventloop.cpp @@ -0,0 +1,125 @@ +/** @file + * + * Copyright 2022 Tomasz Mon + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include +#include "glib_mainloop_on_qeventloop.h" + +GLibPoller::GLibPoller(GMainContext *context) : + mutex_(), dispatched_(), + ctx_(context), priority_(0), + fds_(g_new(GPollFD, 1)), allocated_fds_(1), nfds_(0) +{ + g_main_context_ref(ctx_); +} + +GLibPoller::~GLibPoller() +{ + g_main_context_unref(ctx_); + g_free(fds_); +} + +void GLibPoller::run() +{ + gint timeout; + + mutex_.lock(); + while (!isInterruptionRequested()) + { + while (!g_main_context_acquire(ctx_)) + { + /* In normal circumstances context is acquired right away */ + } + g_main_context_prepare(ctx_, &priority_); + while ((nfds_ = g_main_context_query(ctx_, priority_, &timeout, fds_, + allocated_fds_)) > allocated_fds_) + { + g_free(fds_); + fds_ = g_new(GPollFD, nfds_); + allocated_fds_ = nfds_; + } + /* Blocking g_poll() call is the reason for separate polling thread */ + g_poll(fds_, nfds_, timeout); + g_main_context_release(ctx_); + + /* Polling has finished, dispatch events (if any) in main thread so we + * don't have to worry about concurrency issues in GLib callbacks. + */ + emit polled(); + /* Wait for the main thread to finish dispatching before next poll */ + dispatched_.wait(&mutex_); + } + mutex_.unlock(); +} + +GLibMainloopOnQEventLoop::GLibMainloopOnQEventLoop(QObject *parent) : + QObject(parent), + poller_(g_main_context_default()) +{ + connect(&poller_, &GLibPoller::polled, + this, &GLibMainloopOnQEventLoop::checkAndDispatch); + poller_.setObjectName("GLibPoller"); + poller_.start(); +} + +GLibMainloopOnQEventLoop::~GLibMainloopOnQEventLoop() +{ + poller_.requestInterruption(); + /* Wakeup poller thread in case it is blocked on g_poll(). Wakeup does not + * cause any problem if poller thread is already waiting on dispatched wait + * condition. + */ + g_main_context_wakeup(poller_.ctx_); + /* Wakeup poller thread without actually dispatching */ + poller_.mutex_.lock(); + poller_.dispatched_.wakeOne(); + poller_.mutex_.unlock(); + /* Poller thread will quit, wait for it to avoid warning */ + poller_.wait(); +} + +void GLibMainloopOnQEventLoop::checkAndDispatch() +{ + poller_.mutex_.lock(); + while (!g_main_context_acquire(poller_.ctx_)) + { + /* In normal circumstances context is acquired right away */ + } + if (g_main_depth() > 0) + { + /* This should not happen, but if it does warn about nested event loops + * so the issue can be fixed before the harm is done. To identify root + * cause, put breakpoint here and take backtrace when it hits. Look for + * calls to exec() and processEvents() functions. Refactor the code so + * it does not spin additional event loops. + * + * Ignoring this warning will lead to very strange and hard to debug + * problems in the future. + */ + qWarning("Nested GLib event loop detected"); + } + if (g_main_context_check(poller_.ctx_, poller_.priority_, + poller_.fds_, poller_.nfds_)) + { + g_main_context_dispatch(poller_.ctx_); + } + g_main_context_release(poller_.ctx_); + /* Start next iteration in GLibPoller thread */ + poller_.dispatched_.wakeOne(); + poller_.mutex_.unlock(); +} + +void GLibMainloopOnQEventLoop::setup(QObject *parent) +{ + /* Schedule event loop action so we can check if Qt runs GLib mainloop */ + QTimer::singleShot(0, [parent]() { + if (g_main_depth() == 0) + { + /* Not running inside GLib mainloop, actually setup */ + new GLibMainloopOnQEventLoop(parent); + } + }); +} diff --git a/ui/qt/glib_mainloop_on_qeventloop.h b/ui/qt/glib_mainloop_on_qeventloop.h new file mode 100644 index 0000000000..9f66c68164 --- /dev/null +++ b/ui/qt/glib_mainloop_on_qeventloop.h @@ -0,0 +1,57 @@ +/** @file + * + * Copyright 2022 Tomasz Mon + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef GLIB_MAINLOOP_ON_QEVENTLOOP_H +#define GLIB_MAINLOOP_ON_QEVENTLOOP_H + +#include +#include +#include +#include + +class GLibPoller : public QThread +{ + Q_OBJECT + +protected: + explicit GLibPoller(GMainContext *context); + ~GLibPoller(); + + void run() override; + + QMutex mutex_; + QWaitCondition dispatched_; + GMainContext *ctx_; + gint priority_; + GPollFD *fds_; + gint allocated_fds_, nfds_; + +signals: + void polled(void); + + friend class GLibMainloopOnQEventLoop; +}; + +class GLibMainloopOnQEventLoop : public QObject +{ + Q_OBJECT + +protected: + explicit GLibMainloopOnQEventLoop(QObject *parent); + ~GLibMainloopOnQEventLoop(); + +protected slots: + void checkAndDispatch(); + +public: + static void setup(QObject *parent); + +protected: + GLibPoller poller_; +}; + +#endif /* GLIB_MAINLOOP_ON_QEVENTLOOP_H */ diff --git a/ui/qt/main.cpp b/ui/qt/main.cpp index f6a03bd05c..9b2c3cb31b 100644 --- a/ui/qt/main.cpp +++ b/ui/qt/main.cpp @@ -81,6 +81,7 @@ #include "ui/qt/utils/color_utils.h" #include "ui/qt/coloring_rules_dialog.h" #include "ui/qt/endpoint_dialog.h" +#include "ui/qt/glib_mainloop_on_qeventloop.h" #include "ui/qt/wireshark_main_window.h" #include "ui/qt/response_time_delay_dialog.h" #include "ui/qt/service_response_time_dialog.h" @@ -689,6 +690,8 @@ int main(int argc, char *qt_argv[]) // Init the main window (and splash) main_w = new(WiresharkMainWindow); main_w->show(); + // Setup GLib mainloop on Qt event loop to enable GLib and GIO watches + GLibMainloopOnQEventLoop::setup(main_w); // We may not need a queued connection here but it would seem to make sense // to force the issue. main_w->connect(&ws_app, SIGNAL(openCaptureFile(QString,QString,unsigned int)),