From eae9ff0ab058fd48b94dd181d8c6bd75d609b0ac Mon Sep 17 00:00:00 2001
From: Andy Holmes <andrew.g.r.holmes@gmail.com>
Date: Sun, 20 Aug 2023 15:16:46 -0700
Subject: [PATCH] libworkbench: implement GLogWriterFunc and library
 initialization

Add `Workbench.init()` to initialize resources and other setup tasks.

Currently, this only implements a `GLogWriterFunc` for silencing
specific messages that Workbench users shouldn't see.

This method has some limitations running on other threads in GJS,
and should be more performant in C anyways.
---
 src/init.js                         |   4 +
 src/libworkbench/meson.build        |   2 +
 src/libworkbench/workbench-global.c | 121 ++++++++++++++++++++++++++++
 src/libworkbench/workbench-global.h |  18 +++++
 src/libworkbench/workbench.h        |   1 +
 src/log_handler.js                  | 119 ---------------------------
 src/main.js                         |   1 -
 7 files changed, 146 insertions(+), 120 deletions(-)
 create mode 100644 src/libworkbench/workbench-global.c
 create mode 100644 src/libworkbench/workbench-global.h
 delete mode 100644 src/log_handler.js

diff --git a/src/init.js b/src/init.js
index d433c97e7..d5d5d175f 100644
--- a/src/init.js
+++ b/src/init.js
@@ -12,6 +12,10 @@ import Adw from "gi://Adw";
 import Xdp from "gi://Xdp";
 import Source from "gi://GtkSource";
 import WebKit from "gi://WebKit";
+import Workbench from "gi://Workbench";
+
+
+Workbench.init();
 
 Gio._promisify(Adw.MessageDialog.prototype, "choose", "choose_finish");
 Gio._promisify(Xdp.Portal.prototype, "trash_file", "trash_file_finish");
diff --git a/src/libworkbench/meson.build b/src/libworkbench/meson.build
index 2764b3de4..75db65d95 100644
--- a/src/libworkbench/meson.build
+++ b/src/libworkbench/meson.build
@@ -61,11 +61,13 @@ libworkbench_headers = files([
   'workbench.h',
   'workbench-completion-provider.h',
   'workbench-completion-request.h',
+  'workbench-global.h',
 ])
 
 libworkbench_sources = files([
   'workbench-completion-provider.c',
   'workbench-completion-request.c',
+  'workbench-global.c',
 ])
 
 install_headers(libworkbench_headers,
diff --git a/src/libworkbench/workbench-global.c b/src/libworkbench/workbench-global.c
new file mode 100644
index 000000000..1942483d4
--- /dev/null
+++ b/src/libworkbench/workbench-global.c
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: Workbench Contributors
+// SPDX-FileContributor: Andy Holmes <andyholmes@gnome.org>
+
+#include <glib.h>
+
+#include "workbench-global.h"
+
+
+/*
+ * Silenced log messages
+ */
+struct
+{
+  const char     *log_domain;
+  GLogLevelFlags  log_level;
+  const char     *message;
+} ignored_messages[] = {
+    /* GDK4 */
+    {
+      "Gdk",
+      G_LOG_LEVEL_CRITICAL,
+      "gdk_scroll_event_get_direction: assertion 'GDK_IS_EVENT_TYPE (event, GDK_SCROLL)' failed",
+    },
+    {
+      "Gdk",
+      G_LOG_LEVEL_CRITICAL,
+      "gdk_scroll_event_get_direction: assertion 'GDK_IS_EVENT (event)' failed",
+    },
+
+    /* GTK4 */
+    {
+      "Gtk",
+      G_LOG_LEVEL_CRITICAL,
+      "Unable to connect to the accessibility bus at 'unix:path=/run/flatpak/at-spi-bus': Could not connect: No such file or directory",
+    },
+
+    /* Adwaita */
+    {
+      "Adwaita",
+      G_LOG_LEVEL_WARNING,
+      "Using GtkSettings:gtk-application-prefer-dark-theme with libadwaita is unsupported. Please use AdwStyleManager:color-scheme instead.",
+    },
+
+    /* GVFS */
+    {
+      "GVFS",
+      G_LOG_LEVEL_WARNING,
+      "The peer-to-peer connection failed: Error when getting information for file “/run/user/1000/gvfsd”: No such file or directory. Falling back to the session bus. Your application is probably missing --filesystem=xdg-run/gvfsd privileges.",
+    },
+};
+
+static inline gboolean
+workbench_log_ignore (const char     *log_domain,
+                      GLogLevelFlags  log_level,
+                      const char     *message)
+{
+  if G_UNLIKELY (log_domain == NULL || message == NULL)
+    return FALSE;
+
+  for (unsigned int i = 0; i < G_N_ELEMENTS (ignored_messages); i++)
+    {
+      if (ignored_messages[i].log_level == log_level &&
+          g_str_equal (ignored_messages[i].log_domain, log_domain) &&
+          g_str_equal (ignored_messages[i].message, message))
+        return TRUE;
+    }
+
+  return FALSE;
+}
+
+static GLogWriterOutput
+workbench_log_writer (GLogLevelFlags   log_level,
+                      const GLogField *fields,
+                      gsize            n_fields,
+                      gpointer         user_data)
+{
+  const char *log_domain = NULL;
+  const char *message = NULL;
+
+  /* Respect default handling of log levels and domains */
+  if (g_log_writer_default_would_drop (log_level, log_domain))
+    return G_LOG_WRITER_HANDLED;
+
+  /* Silence specific messages */
+  for (gsize i = 0; i < n_fields; i++)
+    {
+      GLogField field = fields[i];
+
+      if (g_str_equal (field.key, "GLIB_DOMAIN"))
+        log_domain = field.value;
+      else if (g_str_equal (field.key, "MESSAGE"))
+        message = field.value;
+
+      if (log_domain != NULL && message != NULL)
+        break;
+    }
+
+  if (workbench_log_ignore (log_domain, log_level, message))
+    return G_LOG_WRITER_HANDLED;
+
+  return g_log_writer_standard_streams (log_level, fields, n_fields, user_data);
+}
+
+
+/**
+ * workbench_init:
+ *
+ * Initialize the internal library for Workbench.
+ */
+void
+workbench_init (void)
+{
+  gsize initialized = 0;
+
+  if (g_once_init_enter (&initialized))
+    {
+      g_log_set_writer_func (workbench_log_writer, NULL, NULL);
+      g_once_init_leave (&initialized, TRUE);
+    }
+}
diff --git a/src/libworkbench/workbench-global.h b/src/libworkbench/workbench-global.h
new file mode 100644
index 000000000..f51e7905d
--- /dev/null
+++ b/src/libworkbench/workbench-global.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-3.0-only
+// SPDX-FileCopyrightText: Workbench Contributors
+// SPDX-FileContributor: Andy Holmes <andyholmes@gnome.org>
+
+#pragma once
+
+#if !defined (WORKBENCH_INSIDE) && !defined (WORKBENCH_COMPILATION)
+# error "Only <workbench.h> can be included directly."
+#endif
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+WORKBENCH_EXPORT
+void   workbench_init (void);
+
+G_END_DECLS
diff --git a/src/libworkbench/workbench.h b/src/libworkbench/workbench.h
index f0257ecfb..e6e001f98 100644
--- a/src/libworkbench/workbench.h
+++ b/src/libworkbench/workbench.h
@@ -10,6 +10,7 @@
 
 #include "workbench-completion-provider.h"
 #include "workbench-completion-request.h"
+#include "workbench-global.h"
 
 #undef WORKBENCH_INSIDE
 
diff --git a/src/log_handler.js b/src/log_handler.js
deleted file mode 100644
index 8b350c8ca..000000000
--- a/src/log_handler.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import GLib from "gi://GLib";
-
-// Does not wok for some reason
-// const all_log_levels =
-//   GLib.LogLevelFlags.LEVEL_MASK &
-//   GLib.LogLevelFlags.FLAG_FATAL &
-//   GLib.LogLevelFlags.FLAG_RECURSION;
-
-const all_log_levels =
-  GLib.LogLevelFlags.FLAG_FATAL |
-  GLib.LogLevelFlags.FLAG_RECURSION |
-  GLib.LogLevelFlags.LEVEL_CRITICAL |
-  GLib.LogLevelFlags.LEVEL_DEBUG |
-  GLib.LogLevelFlags.LEVEL_ERROR |
-  GLib.LogLevelFlags.LEVEL_INFO |
-  // GLib.LogLevelFlags.LEVEL_MASK |
-  GLib.LogLevelFlags.LEVEL_MESSAGE |
-  GLib.LogLevelFlags.LEVEL_WARNING;
-
-/* Cannot use GLib.log_set_writer_func because it is not safe to use https://gitlab.gnome.org/GNOME/gjs/-/issues/481 */
-// const decoder = new TextDecoder();
-// GLib.log_set_writer_func((level, fields) => {
-//   const domain = decoder.decode(fields.GLIB_DOMAIN);
-//   const message = decoder.decode(fields.MESSAGE);
-//   log_handler(domain, level, message);
-//   return GLib.LogWriterOutput.HANDLED;
-// });
-
-// Not working - Gjs-Console uses structured logging
-// GLib.log_set_handler("Gjs-Console", all_log_levels, log_handler);
-GLib.log_set_handler("Gdk", all_log_levels, log_handler);
-GLib.log_set_handler("Adwaita", all_log_levels, log_handler);
-GLib.log_set_handler("GVFS", all_log_levels, log_handler);
-GLib.log_set_handler("Workbench", all_log_levels, log_handler);
-// Not working - Gtk is probably using structured logging
-// GLib.log_set_handler("Gtk", all_log_levels, log_handler);
-
-// https://docs.gtk.org/glib/flags.LogLevelFlags.html
-// https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
-function get_log_level_name(log_level, domain) {
-  switch (log_level) {
-    case GLib.LogLevelFlags.FLAG_RECURSION:
-      return "\x1b[1;31mRecursion\x1b[0m";
-    case GLib.LogLevelFlags.FLAG_FATAL:
-      return "\x1b[1;31mFatal\x1b[0m";
-    case GLib.LogLevelFlags.LEVEL_CRITICAL:
-      // This is what console.error use
-      return domain === "Gjs-Console"
-        ? "\x1b[1;31mError\x1b[0m"
-        : "\x1b[1;35mCritical\x1b[0m";
-    case GLib.LogLevelFlags.LEVEL_ERROR:
-      return "\x1b[1;31mError\x1b[0m";
-    case GLib.LogLevelFlags.LEVEL_WARNING:
-      return "\x1b[1;33mWarning\x1b[0m";
-    // case GLib.LogLevelFlags.LEVEL_MESSAGE:
-    // case GLib.LogLevelFlags.LEVEL_INFO:
-    // case GLib.LogLevelFlags.LEVEL_DEBUG:
-    default:
-      return "";
-  }
-}
-
-function log_handler(domain, level, message) {
-  // if (level === GLib.LogLevelFlags.LEVEL_DEBUG) {
-  //   return GLib.LogWriterOutput.HANDLED;
-  // }
-
-  if (
-    domain === "Gdk" &&
-    level === GLib.LogLevelFlags.LEVEL_CRITICAL &&
-    [
-      "gdk_scroll_event_get_direction: assertion 'GDK_IS_EVENT_TYPE (event, GDK_SCROLL)' failed",
-      "gdk_scroll_event_get_direction: assertion 'GDK_IS_EVENT (event)' failed",
-    ].includes(message)
-  ) {
-    return GLib.LogWriterOutput.HANDLED;
-  }
-
-  if (
-    domain === "Gtk" &&
-    level === GLib.LogLevelFlags.LEVEL_CRITICAL &&
-    message ===
-      "Unable to connect to the accessibility bus at 'unix:path=/run/flatpak/at-spi-bus': Could not connect: No such file or directory"
-  ) {
-    return GLib.LogWriterOutput.HANDLED;
-  }
-
-  if (
-    domain === "Adwaita" &&
-    level === GLib.LogLevelFlags.LEVEL_WARNING &&
-    message ===
-      "Using GtkSettings:gtk-application-prefer-dark-theme with libadwaita is unsupported. Please use AdwStyleManager:color-scheme instead."
-  ) {
-    return GLib.LogWriterOutput.HANDLED;
-  }
-
-  if (
-    domain === "GVFS" &&
-    level === GLib.LogLevelFlags.LEVEL_WARNING &&
-    message ===
-      "The peer-to-peer connection failed: Error when getting information for file “/run/user/1000/gvfsd”: No such file or directory. Falling back to the session bus. Your application is probably missing --filesystem=xdg-run/gvfsd privileges."
-  ) {
-    return GLib.LogWriterOutput.HANDLED;
-  }
-
-  let str = "\n";
-
-  if (!["Gjs", "Gjs-Console"].includes(domain)) {
-    str += `${domain}-`;
-  }
-
-  const level_name = get_log_level_name(level, domain);
-  str += level_name ? `${level_name}: ` : "";
-  str += message;
-  str += "\n";
-
-  // console.terminal.fork_command(`echo ${str}`);
-  print(str);
-}
diff --git a/src/main.js b/src/main.js
index 53bd97214..040ef5839 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,5 +1,4 @@
 import "./init.js";
-import "./log_handler.js";
 import application from "./application.js";
 
 pkg.initGettext();