diff --git a/include/wx/gtk/private/log.h b/include/wx/gtk/private/log.h new file mode 100644 index 000000000000..662342e3731e --- /dev/null +++ b/include/wx/gtk/private/log.h @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////////// +// Name: wx/gtk/private/log.h +// Purpose: Support for filtering GTK log messages. +// Author: Vadim Zeitlin +// Created: 2022-05-11 +// Copyright: (c) 2022 Vadim Zeitlin +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _WX_GTK_PRIVATE_LOG_H_ +#define _WX_GTK_PRIVATE_LOG_H_ + +#include + +// Support for custom log writers is only available in glib 2.50 or later. +#if GLIB_CHECK_VERSION(2, 50, 0) + #define wxHAS_GLIB_LOG_WRITER +#endif + +namespace wxGTKImpl +{ + +#ifdef wxHAS_GLIB_LOG_WRITER + +// LogFilter is the base class for filtering GTK log messages +// +// Note that all members of this class are defined in src/gtk/app.cpp. +class LogFilter +{ +public: + LogFilter() + { + m_next = NULL; + } + + // Function to call to install this filter as the active one. + // Does nothing and just returns false if run-time glib version is too old. + bool Install(); + +protected: + // Function to override in the derived class to actually filter: return + // true if the message should be suppressed or false if it should be passed + // through to the default writer (which may, or not, show it). + virtual bool Filter(GLogLevelFlags log_level, + const GLogField* fields, + gsize n_fields) const = 0; + + // Typically called from the derived class dtor to stop using this filter. + void Uninstall(); + +private: + // The function used as glib log writer. + static GLogWriterOutput + wx_log_writer(GLogLevelFlags log_level, + const GLogField *fields, + gsize n_fields, + gpointer user_data); + + // False initially, set to true when we install wx_log_writer() as the log + // writer. Once we do it, we never change it any more. + static bool ms_installed; + + // We maintain a simple linked list of log filters and this is the head of + // this list. + static LogFilter* ms_first; + + // Next entry in the linked list, may be null. + LogFilter* m_next; + + wxDECLARE_NO_COPY_CLASS(LogFilter); +}; + +// LogFilterByLevel filters out all the messages at the specified level. +class LogFilterByLevel : public LogFilter +{ +public: + LogFilterByLevel() { } + + void SetLevelToIgnore(int flags) + { + m_logLevelToIgnore = flags; + } + +protected: + bool Filter(GLogLevelFlags log_level, + const GLogField* WXUNUSED(fields), + gsize WXUNUSED(n_fields)) const wxOVERRIDE + { + return log_level & m_logLevelToIgnore; + } + +private: + int m_logLevelToIgnore; + + wxDECLARE_NO_COPY_CLASS(LogFilterByLevel); +}; + +// LogFilterByMessage filters out all the messages with the specified content. +class LogFilterByMessage : public LogFilter +{ +public: + // Objects of this class are supposed to be created with literal strings as + // argument, so don't bother copying the string but just use the pointer. + explicit LogFilterByMessage(const char* message) + : m_message(message) + { + // We shouldn't warn about anything if Install() failed. + m_warnNotFiltered = Install(); + } + + // Remove this filter when the object goes out of scope. + // + // The dtor also checks if we actually filtered the message and logs a + // trace message with the "gtklog" mask if we didn't: this allows checking + // if the filter is actually being used. + ~LogFilterByMessage(); + +protected: + bool Filter(GLogLevelFlags WXUNUSED(log_level), + const GLogField* fields, + gsize n_fields) const wxOVERRIDE; + +private: + const char* const m_message; + + mutable bool m_warnNotFiltered; + + wxDECLARE_NO_COPY_CLASS(LogFilterByMessage); +}; + +#endif // wxHAS_GLIB_LOG_WRITER + +} // namespace wxGTKImpl + +#endif // _WX_GTK_PRIVATE_LOG_H_ diff --git a/src/gtk/app.cpp b/src/gtk/app.cpp index ab392b150e44..4e8bd2926c0a 100644 --- a/src/gtk/app.cpp +++ b/src/gtk/app.cpp @@ -30,6 +30,7 @@ #include "wx/msgout.h" #include "wx/gtk/private.h" +#include "wx/gtk/private/log.h" #include "wx/gtk/mimetype.h" //----------------------------------------------------------------------------- @@ -178,48 +179,112 @@ bool wxApp::DoIdle() return keepSource; } -// Custom Glib log writer: setting it is only possible with glib 2.50 or later. -#if GLIB_CHECK_VERSION(2, 50, 0) -extern "C" { -static GLogWriterOutput -wx_log_writer(GLogLevelFlags log_level, - const GLogField *fields, - gsize n_fields, - gpointer user_data) +#ifdef wxHAS_GLIB_LOG_WRITER + +namespace wxGTKImpl { - const wxUIntPtr log_mask = reinterpret_cast(user_data); - GLogWriterOutput result; - if (log_level & log_mask) +bool LogFilter::ms_installed = false; +LogFilter* LogFilter::ms_first = NULL; + +/* static */ +GLogWriterOutput +LogFilter::wx_log_writer(GLogLevelFlags log_level, + const GLogField *fields, + gsize n_fields, + gpointer WXUNUSED(user_data)) +{ + for ( const LogFilter* lf = LogFilter::ms_first; lf; lf = lf->m_next ) { - result = G_LOG_WRITER_HANDLED; + if ( lf->Filter(log_level, fields, n_fields) ) + return G_LOG_WRITER_HANDLED; } - else + + return g_log_writer_default(log_level, fields, n_fields, NULL); +} + +bool LogFilter::Install() +{ + if ( !ms_installed ) { - result = g_log_writer_default(log_level, fields, n_fields, NULL); + if ( glib_check_version(2, 50, 0) != 0 ) + { + // No runtime support for log callback, we can't do anything. + return false; + } + + g_log_set_writer_func(LogFilter::wx_log_writer, NULL, NULL); + ms_installed = true; } - return result; + + // Put this object in front of the linked list. + m_next = ms_first; + ms_first = this; + + return true; } + +void LogFilter::Uninstall() +{ + if ( !ms_installed ) + { + // We don't do anything at all in this case. + return; + } + + // We should be uninstalling only the currently installed filter. + wxASSERT( ms_first == this ); + + ms_first = m_next; } -/* static */ -void wxApp::GTKSuppressDiagnostics(int flags) +bool LogFilterByMessage::Filter(GLogLevelFlags WXUNUSED(log_level), + const GLogField* fields, + gsize n_fields) const { - if (glib_check_version(2, 50, 0) == 0) + for ( gsize n = 0; n < n_fields; ++n ) { - g_log_set_writer_func( - wx_log_writer, - (wxUIntToPtr)(flags == -1 ? G_LOG_LEVEL_MASK : flags), - NULL); + const GLogField& f = fields[n]; + if ( strcmp(f.key, "MESSAGE") == 0 ) + { + if ( strcmp(static_cast(f.value), m_message) == 0 ) + { + // This is the message we want to filter. + m_warnNotFiltered = false; + return true; + } + } } + + return false; +} + +LogFilterByMessage::~LogFilterByMessage() +{ + Uninstall(); + + if ( m_warnNotFiltered ) + { + wxLogTrace("gtklog", "Message \"%s\" wasn't logged.", m_message); + } +} + +} // namespace wxGTKImpl + +/* static */ +void wxApp::GTKSuppressDiagnostics(int flags) +{ + static wxGTKImpl::LogFilterByLevel s_logFilter; + s_logFilter.SetLevelToIgnore(flags); + s_logFilter.Install(); } -#else // glib < 2.50 +#else // !wxHAS_GLIB_LOG_WRITER /* static */ void wxApp::GTKSuppressDiagnostics(int WXUNUSED(flags)) { // We can't do anything here. } -#endif // glib >=/< 2.50 +#endif // wxHAS_GLIB_LOG_WRITER/!wxHAS_GLIB_LOG_WRITER //----------------------------------------------------------------------------- // wxApp diff --git a/src/gtk/notebook.cpp b/src/gtk/notebook.cpp index 3b7eb94cd1db..616d028c2c86 100644 --- a/src/gtk/notebook.cpp +++ b/src/gtk/notebook.cpp @@ -26,6 +26,7 @@ #include "wx/gtk/private.h" #include "wx/gtk/private/image.h" +#include "wx/gtk/private/log.h" #include "wx/gtk/private/stylecontext.h" //----------------------------------------------------------------------------- @@ -426,6 +427,12 @@ wxNotebookPage *wxNotebook::DoRemovePage( size_t page ) if ( !client ) return NULL; + // Suppress bogus assertion failures happening deel inside ATK that can't + // be avoided in any other way, see #22176. + wxGTKImpl::LogFilterByMessage filterLog( + "gtk_notebook_get_tab_label: assertion 'list != NULL' failed" + ); + // we don't need to unparent the client->m_widget; GTK+ will do // that for us (and will throw a warning if we do it!) gtk_notebook_remove_page( GTK_NOTEBOOK(m_widget), page );