@gjsify/node-gi 0.58.0 → 0.59.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,187 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Android only: route GLib's messages into logcat.
3
+ //
4
+ // On Android an app's stdout/stderr go to /dev/null unless something redirects them,
5
+ // so GLib's default handlers — which write to stderr — lose every g_warning,
6
+ // g_critical and g_message a GI call produces. The failure mode is the expensive one:
7
+ // GTK reports precisely why it refused to start, and the report never leaves the
8
+ // process. GTK's own Android runtime installs these four handlers in startRuntime()
9
+ // (refs/gtk/gdk/android/gdkandroidruntime.c), but startRuntime also loads an
10
+ // application library and spawns GTK's own thread, so a node-gi host must not call
11
+ // it — this is the piece of it that a host still needs.
12
+ //
13
+ // Installed from Init, not on request: a host that has not asked for logging still
14
+ // wants to be told why GI failed, and there is no other sink on this platform.
15
+ // g_log_set_default_handler / g_log_set_writer_func are process-global and
16
+ // last-call-wins, so a later logSetWriterFunc (private.cc) still overrides this.
17
+ //
18
+ // GLib is not the only writer: a foreign C library that does fprintf(stderr, ...) and
19
+ // abort() (libepoxy's "eglQueryDisplayAttribEXT() not found" was the case) never reaches
20
+ // a GLib handler. So fd 1 and fd 2 themselves are pointed at pipes that a reader thread
21
+ // forwards to logcat line by line.
22
+
23
+ #include "common.h"
24
+
25
+ #ifdef __ANDROID__
26
+
27
+ #include <android/log.h>
28
+ #include <errno.h>
29
+ #include <fcntl.h>
30
+ #include <sys/stat.h>
31
+ #include <unistd.h>
32
+
33
+ #include <cstdio>
34
+ #include <mutex>
35
+ #include <string>
36
+ #include <thread>
37
+
38
+ namespace nodegi {
39
+
40
+ // Whether `fd` is the /dev/null an app process starts with. Anything else (a terminal
41
+ // under `adb shell`, a file or pipe a host or the launcher set up) is a sink somebody
42
+ // chose, and taking it over would hide output from them.
43
+ static bool IsDevNull(int fd) {
44
+ struct stat fd_st, null_st;
45
+ return fstat(fd, &fd_st) == 0 && stat("/dev/null", &null_st) == 0 && S_ISCHR(fd_st.st_mode) &&
46
+ fd_st.st_rdev == null_st.st_rdev;
47
+ }
48
+
49
+ // Longest line forwarded whole; logcat truncates an entry near 4 KiB anyway, and a
50
+ // writer that never sends '\n' must not grow the buffer without bound.
51
+ static const size_t kMaxLine = 4000;
52
+
53
+ struct StdioPipe {
54
+ int read_fd;
55
+ int priority;
56
+ const char* tag;
57
+ };
58
+
59
+ static void ForwardLoop(StdioPipe pipe_info) {
60
+ std::string line;
61
+ char buf[1024];
62
+ for (;;) {
63
+ ssize_t n = read(pipe_info.read_fd, buf, sizeof buf);
64
+ if (n < 0 && errno == EINTR) continue;
65
+ if (n <= 0) break;
66
+ for (ssize_t i = 0; i < n; i++) {
67
+ if (buf[i] == '\n') {
68
+ __android_log_write(pipe_info.priority, pipe_info.tag, line.c_str());
69
+ line.clear();
70
+ } else {
71
+ line.push_back(buf[i]);
72
+ if (line.size() >= kMaxLine) {
73
+ __android_log_write(pipe_info.priority, pipe_info.tag, line.c_str());
74
+ line.clear();
75
+ }
76
+ }
77
+ }
78
+ }
79
+ if (!line.empty()) __android_log_write(pipe_info.priority, pipe_info.tag, line.c_str());
80
+ }
81
+
82
+ static void RedirectFd(int fd, int priority, const char* tag) {
83
+ if (!IsDevNull(fd)) return;
84
+ int fds[2];
85
+ if (pipe2(fds, O_CLOEXEC) != 0) return;
86
+ if (dup2(fds[1], fd) < 0) {
87
+ close(fds[0]);
88
+ close(fds[1]);
89
+ return;
90
+ }
91
+ close(fds[1]);
92
+ // Two pipes rather than one: priority is per entry, and stderr must stay WARN so a
93
+ // logcat filter on `*:W` still shows it.
94
+ std::thread(ForwardLoop, StdioPipe{fds[0], priority, tag}).detach();
95
+ }
96
+
97
+ // Once per process: Init runs once per env, but fd 1/2 are process-wide.
98
+ //
99
+ // Limit: abort() ends the process, and the reader thread with it. stderr is made
100
+ // unbuffered so the message at least reaches the pipe before the abort, but whether the
101
+ // reader has forwarded it by then is a race the tombstone still backs up.
102
+ static void InstallStdioRedirect() {
103
+ static std::once_flag once;
104
+ std::call_once(once, [] {
105
+ RedirectFd(STDOUT_FILENO, ANDROID_LOG_INFO, "stdout");
106
+ RedirectFd(STDERR_FILENO, ANDROID_LOG_WARN, "stderr");
107
+ setvbuf(stdout, nullptr, _IOLBF, 0);
108
+ setvbuf(stderr, nullptr, _IONBF, 0);
109
+ });
110
+ }
111
+
112
+ static void AndroidPrintHandler(const char* message) {
113
+ __android_log_print(ANDROID_LOG_INFO, "print", "%s", message);
114
+ }
115
+
116
+ static void AndroidPrinterrHandler(const char* message) {
117
+ __android_log_print(ANDROID_LOG_WARN, "print", "%s", message);
118
+ }
119
+
120
+ static int AndroidPriority(GLogLevelFlags log_level) {
121
+ if (log_level & G_LOG_LEVEL_ERROR) return ANDROID_LOG_FATAL;
122
+ if (log_level & G_LOG_LEVEL_CRITICAL) return ANDROID_LOG_ERROR;
123
+ if (log_level & G_LOG_LEVEL_WARNING) return ANDROID_LOG_WARN;
124
+ if (log_level & G_LOG_LEVEL_MESSAGE) return ANDROID_LOG_INFO;
125
+ if (log_level & G_LOG_LEVEL_INFO) return ANDROID_LOG_INFO;
126
+ if (log_level & G_LOG_LEVEL_DEBUG) return ANDROID_LOG_DEBUG;
127
+ return ANDROID_LOG_WARN;
128
+ }
129
+
130
+ static void AndroidLogHandler(const gchar* log_domain, GLogLevelFlags log_level,
131
+ const gchar* message, gpointer) {
132
+ __android_log_print(AndroidPriority(log_level),
133
+ log_domain != nullptr ? log_domain : "**", "%s", message);
134
+ }
135
+
136
+ static GLogWriterOutput AndroidLogWriter(GLogLevelFlags log_level, const GLogField* fields,
137
+ gsize n_fields, gpointer) {
138
+ const gchar* domain = nullptr;
139
+ const gchar* message = nullptr;
140
+ for (gsize i = 0; (domain == nullptr || message == nullptr) && i < n_fields; i++) {
141
+ if (g_strcmp0(fields[i].key, "GLIB_DOMAIN") == 0)
142
+ domain = static_cast<const gchar*>(fields[i].value);
143
+ else if (g_strcmp0(fields[i].key, "MESSAGE") == 0)
144
+ message = static_cast<const gchar*>(fields[i].value);
145
+ }
146
+ // liblog answers with the bytes it wrote, or a negative errno: anything above zero is
147
+ // a delivery. Reporting UNHANDLED on one sends GLib to its default writer, i.e. to the
148
+ // stderr this whole file exists because of.
149
+ int rc = __android_log_print(AndroidPriority(log_level),
150
+ domain != nullptr ? domain : "**", "%s",
151
+ message != nullptr ? message : "(empty)");
152
+ return rc > 0 ? G_LOG_WRITER_HANDLED : G_LOG_WRITER_UNHANDLED;
153
+ }
154
+
155
+ // androidLogBridge() -> boolean — whether GLib's messages reach logcat.
156
+ static Napi::Value AndroidLogBridge(const Napi::CallbackInfo& info) {
157
+ return Napi::Boolean::New(info.Env(), true);
158
+ }
159
+
160
+ void InitAndroidLog(Napi::Env env, Napi::Object exports) {
161
+ g_set_print_handler(AndroidPrintHandler);
162
+ g_set_printerr_handler(AndroidPrinterrHandler);
163
+ g_log_set_default_handler(AndroidLogHandler, nullptr);
164
+ g_log_set_writer_func(AndroidLogWriter, nullptr, nullptr);
165
+ InstallStdioRedirect();
166
+ exports.Set("androidLogBridge", Napi::Function::New(env, AndroidLogBridge));
167
+ }
168
+
169
+ } // namespace nodegi
170
+
171
+ #else
172
+
173
+ namespace nodegi {
174
+
175
+ static Napi::Value AndroidLogBridge(const Napi::CallbackInfo& info) {
176
+ return Napi::Boolean::New(info.Env(), false);
177
+ }
178
+
179
+ // Off Android stderr is a real sink and GLib's own handlers are the right ones; the
180
+ // export stays so a caller can ask without branching on the platform first.
181
+ void InitAndroidLog(Napi::Env env, Napi::Object exports) {
182
+ exports.Set("androidLogBridge", Napi::Function::New(env, AndroidLogBridge));
183
+ }
184
+
185
+ } // namespace nodegi
186
+
187
+ #endif
package/src/common.h CHANGED
@@ -19,11 +19,28 @@
19
19
  #ifndef NODE_GI_SRC_COMMON_H_
20
20
  #define NODE_GI_SRC_COMMON_H_
21
21
 
22
+ // NODE_GI_HAS_LIBUV — may this build REFERENCE libuv at all? Not "is there an
23
+ // event loop": bionic resolves every symbol at dlopen, so a single
24
+ // uv_timer_start left in the object file makes the whole addon fail to load on
25
+ // Android, whatever the call site does at runtime. The Android host is
26
+ // NativeScript's V8 runtime, a Node-API host with no libuv of its own
27
+ // (napi_get_uv_event_loop always fails there), so the uv bridge in loop.cc is
28
+ // compiled out and the ALooper pump beside it takes over.
29
+ #ifndef NODE_GI_HAS_LIBUV
30
+ #ifdef __ANDROID__
31
+ #define NODE_GI_HAS_LIBUV 0
32
+ #else
33
+ #define NODE_GI_HAS_LIBUV 1
34
+ #endif
35
+ #endif
36
+
22
37
  #ifndef _WIN32
23
38
  #include <dlfcn.h> // dlopen/dlsym the GtkWidgetClass template API (no GTK link)
24
39
  #endif
25
40
  #include <napi.h>
41
+ #if NODE_GI_HAS_LIBUV
26
42
  #include <uv.h>
43
+ #endif
27
44
 
28
45
  #include <girepository/girepository.h>
29
46
  #include <girepository/girffi.h> // gi_callable_info_create_closure + ffi_cif
@@ -178,6 +195,12 @@ const ForeignStructOps* ForeignOpsForInfo(GIBaseInfo* iface);
178
195
  void InitCairo(Napi::Env env, Napi::Object exports);
179
196
  // win32: probeHostOpenGL / preloadOpenGL / currentGLStrings (opengl-win32.cc); a no-op elsewhere.
180
197
  void InitOpenGLWin32(Napi::Env env, Napi::Object exports);
198
+ // Android: install GLib's print/log/writer handlers onto logcat (android-log.cc), where
199
+ // stderr goes nowhere. Elsewhere only the `androidLogBridge` probe, answering false.
200
+ void InitAndroidLog(Napi::Env env, Napi::Object exports);
201
+ // Android: androidInitGdk(), the opt-in GDK bring-up that replaces GTK's
202
+ // RuntimeApplication (android-gdk.cc). Elsewhere the export answers false.
203
+ void InitAndroidGdk(Napi::Env env, Napi::Object exports);
181
204
 
182
205
  // ---- GJS-exact 64-bit integer marshalling helpers (shared) -------------------
183
206
  //