Branch data Line data Source code
1 : : /* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : /*
3 : : * SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
4 : : * SPDX-FileCopyrightText: 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
5 : : */
6 : :
7 : : #include <config.h>
8 : :
9 : : #include <errno.h> /* for errno */
10 : : #include <locale.h> /* for setlocale/duplocale/uselocale/newlocale/freelocale */
11 : : #include <stdbool.h>
12 : : #include <stddef.h> /* for size_t */
13 : : #include <string.h>
14 : :
15 : : #include <glib-object.h>
16 : : #include <girepository.h>
17 : : #include <glib.h>
18 : : #include <glib/gi18n.h> /* for bindtextdomain, bind_textdomain_codeset, textdomain */
19 : :
20 : : #include "libgjs-private/gjs-util.h"
21 : : #include "util/console.h"
22 : :
23 : 5 : GType gjs_locale_category_get_type(void) {
24 : : static size_t gjs_locale_category_get_type = 0;
25 [ + - + - : 5 : if (g_once_init_enter(&gjs_locale_category_get_type)) {
+ - ]
26 : : static const GEnumValue v[] = {
27 : : {GJS_LOCALE_CATEGORY_ALL, "GJS_LOCALE_CATEGORY_ALL", "all"},
28 : : {GJS_LOCALE_CATEGORY_COLLATE, "GJS_LOCALE_CATEGORY_COLLATE",
29 : : "collate"},
30 : : {GJS_LOCALE_CATEGORY_CTYPE, "GJS_LOCALE_CATEGORY_CTYPE", "ctype"},
31 : : {GJS_LOCALE_CATEGORY_MESSAGES, "GJS_LOCALE_CATEGORY_MESSAGES",
32 : : "messages"},
33 : : {GJS_LOCALE_CATEGORY_MONETARY, "GJS_LOCALE_CATEGORY_MONETARY",
34 : : "monetary"},
35 : : {GJS_LOCALE_CATEGORY_NUMERIC, "GJS_LOCALE_CATEGORY_NUMERIC",
36 : : "numeric"},
37 : : {GJS_LOCALE_CATEGORY_TIME, "GJS_LOCALE_CATEGORY_TIME", "time"},
38 : : {0, NULL, NULL}};
39 : 5 : GType g_define_type_id = g_enum_register_static(
40 : : g_intern_static_string("GjsLocaleCategory"), v);
41 : :
42 : 5 : g_once_init_leave(&gjs_locale_category_get_type, g_define_type_id);
43 : : }
44 : 5 : return gjs_locale_category_get_type;
45 : : }
46 : :
47 : : typedef struct {
48 : : locale_t id;
49 : : char* name;
50 : : char* prior_name;
51 : : } GjsLocale;
52 : :
53 : : #define UNSET_LOCALE_ID ((locale_t)0)
54 : :
55 : 4 : static void gjs_clear_locale_id(locale_t* id) {
56 [ - + ]: 4 : if (id == NULL)
57 : 0 : return;
58 : :
59 [ + + ]: 4 : if (*id == UNSET_LOCALE_ID)
60 : 3 : return;
61 : :
62 : 1 : freelocale(*id);
63 : 1 : *id = UNSET_LOCALE_ID;
64 : : }
65 : :
66 : 1 : static locale_t gjs_steal_locale_id(locale_t* id) {
67 : 1 : locale_t stolen_id = *id;
68 : 1 : *id = UNSET_LOCALE_ID;
69 : 1 : return stolen_id;
70 : : }
71 : :
72 : 1 : static gboolean gjs_set_locale_id(locale_t* locale_id_pointer,
73 : : locale_t new_locale_id) {
74 [ - + ]: 1 : if (*locale_id_pointer == new_locale_id)
75 : 0 : return FALSE;
76 : :
77 [ - + ]: 1 : if (*locale_id_pointer != UNSET_LOCALE_ID)
78 : 0 : freelocale(*locale_id_pointer);
79 : 1 : *locale_id_pointer = new_locale_id;
80 : :
81 : 1 : return TRUE;
82 : : }
83 : :
84 : 2 : static int gjs_locale_category_get_mask(GjsLocaleCategory category) {
85 : : /* It's tempting to just return (1 << category) but the header file
86 : : * says not to do that.
87 : : */
88 [ + - - - : 2 : switch (category) {
- - - - ]
89 : 2 : case GJS_LOCALE_CATEGORY_ALL:
90 : 2 : return LC_ALL_MASK;
91 : 0 : case GJS_LOCALE_CATEGORY_COLLATE:
92 : 0 : return LC_COLLATE_MASK;
93 : 0 : case GJS_LOCALE_CATEGORY_CTYPE:
94 : 0 : return LC_CTYPE_MASK;
95 : 0 : case GJS_LOCALE_CATEGORY_MESSAGES:
96 : 0 : return LC_MESSAGES_MASK;
97 : 0 : case GJS_LOCALE_CATEGORY_MONETARY:
98 : 0 : return LC_MONETARY_MASK;
99 : 0 : case GJS_LOCALE_CATEGORY_NUMERIC:
100 : 0 : return LC_NUMERIC_MASK;
101 : 0 : case GJS_LOCALE_CATEGORY_TIME:
102 : 0 : return LC_TIME_MASK;
103 : 0 : default:
104 : 0 : break;
105 : : }
106 : :
107 : 0 : return 0;
108 : : }
109 : 2 : static size_t get_number_of_locale_categories(void) {
110 : 2 : return __builtin_popcount(LC_ALL_MASK) + 1;
111 : : }
112 : :
113 : 0 : static void gjs_locales_free(GjsLocale** locales) {
114 : 0 : size_t number_of_categories = get_number_of_locale_categories();
115 : : size_t i;
116 : :
117 [ # # ]: 0 : for (i = 0; i < number_of_categories; i++) {
118 : 0 : GjsLocale* locale = locales[i];
119 : 0 : gjs_clear_locale_id(&locale->id);
120 [ # # ]: 0 : g_clear_pointer(&locale->name, g_free);
121 [ # # ]: 0 : g_clear_pointer(&locale->prior_name, g_free);
122 : : }
123 : :
124 : 0 : g_free(locales);
125 : 0 : }
126 : :
127 : 2 : static GjsLocale* gjs_locales_new(void) {
128 : 2 : size_t number_of_categories = get_number_of_locale_categories();
129 : : GjsLocale* locales;
130 : :
131 : 2 : locales = g_new0(GjsLocale, number_of_categories);
132 : :
133 : 2 : return locales;
134 : : }
135 : :
136 : : static GPrivate gjs_private_locale_key =
137 : : G_PRIVATE_INIT((GDestroyNotify)gjs_locales_free);
138 : :
139 : : /**
140 : : * gjs_set_thread_locale:
141 : : * @category:
142 : : * @locale: (allow-none):
143 : : *
144 : : * Returns:
145 : : */
146 : 4 : const char* gjs_set_thread_locale(GjsLocaleCategory category,
147 : : const char* locale_name) {
148 : 4 : locale_t new_locale_id = UNSET_LOCALE_ID, old_locale_id = UNSET_LOCALE_ID;
149 : 4 : GjsLocale *locales = NULL, *locale = NULL;
150 : : int category_mask;
151 : 4 : char* prior_name = NULL;
152 : 4 : gboolean success = FALSE;
153 : : int errno_save;
154 : :
155 : 4 : locales = g_private_get(&gjs_private_locale_key);
156 : :
157 [ + + ]: 4 : if (locales == NULL) {
158 : 2 : locales = gjs_locales_new();
159 : 2 : g_private_set(&gjs_private_locale_key, locales);
160 : : }
161 : 4 : locale = &locales[category];
162 : :
163 [ + + ]: 4 : if (locale_name == NULL) {
164 [ - + ]: 2 : if (locale->name != NULL)
165 : 0 : return locale->name;
166 : :
167 : 2 : return setlocale(category, NULL);
168 : : }
169 : :
170 : 2 : old_locale_id = uselocale(UNSET_LOCALE_ID);
171 [ - + ]: 2 : if (old_locale_id == UNSET_LOCALE_ID)
172 : 0 : goto out;
173 : :
174 : 2 : old_locale_id = duplocale(old_locale_id);
175 [ - + ]: 2 : if (old_locale_id == UNSET_LOCALE_ID)
176 : 0 : goto out;
177 : :
178 : 2 : category_mask = gjs_locale_category_get_mask(category);
179 : :
180 [ - + ]: 2 : if (category_mask == 0)
181 : 0 : goto out;
182 : :
183 : 2 : new_locale_id = newlocale(category_mask, locale_name, old_locale_id);
184 : :
185 [ + + ]: 2 : if (new_locale_id == UNSET_LOCALE_ID)
186 : 1 : goto out;
187 : 1 : old_locale_id = UNSET_LOCALE_ID; /* was moved into new_locale_id */
188 : :
189 : 1 : prior_name = g_strdup(setlocale(category, NULL));
190 : :
191 [ - + ]: 1 : if (uselocale(new_locale_id) == UNSET_LOCALE_ID)
192 : 0 : goto out;
193 : :
194 : 1 : g_set_str(&locale->prior_name, prior_name);
195 : 1 : gjs_set_locale_id(&locale->id, gjs_steal_locale_id(&new_locale_id));
196 : 1 : g_set_str(&locale->name, setlocale(category, NULL));
197 : :
198 : 1 : success = TRUE;
199 : 2 : out:
200 [ + + ]: 2 : g_clear_pointer(&prior_name, g_free);
201 : 2 : errno_save = errno;
202 : 2 : gjs_clear_locale_id(&old_locale_id);
203 : 2 : gjs_clear_locale_id(&new_locale_id);
204 : 2 : errno = errno_save;
205 : :
206 [ + + ]: 2 : if (!success)
207 : 1 : return NULL;
208 : :
209 : 1 : return locale->prior_name;
210 : : }
211 : :
212 : : void
213 : 0 : gjs_textdomain(const char *domain)
214 : : {
215 : 0 : textdomain(domain);
216 : 0 : }
217 : :
218 : : void
219 : 0 : gjs_bindtextdomain(const char *domain,
220 : : const char *location)
221 : : {
222 : 0 : bindtextdomain(domain, location);
223 : : /* Always use UTF-8; we assume it internally here */
224 : 0 : bind_textdomain_codeset(domain, "UTF-8");
225 : 0 : }
226 : :
227 : : GParamFlags
228 : 110 : gjs_param_spec_get_flags(GParamSpec *pspec)
229 : : {
230 : 110 : return pspec->flags;
231 : : }
232 : :
233 : : GType
234 : 2 : gjs_param_spec_get_value_type(GParamSpec *pspec)
235 : : {
236 : 2 : return pspec->value_type;
237 : : }
238 : :
239 : : GType
240 : 0 : gjs_param_spec_get_owner_type(GParamSpec *pspec)
241 : : {
242 : 0 : return pspec->owner_type;
243 : : }
244 : :
245 : : #define G_CLOSURE_NOTIFY(func) ((GClosureNotify)(void (*)(void))func)
246 : :
247 : 1 : GBinding* gjs_g_object_bind_property_full(
248 : : GObject* source, const char* source_property, GObject* target,
249 : : const char* target_property, GBindingFlags flags,
250 : : GjsBindingTransformFunc to_callback, void* to_data,
251 : : GDestroyNotify to_notify, GjsBindingTransformFunc from_callback,
252 : : void* from_data, GDestroyNotify from_notify) {
253 : 1 : GClosure* to_closure = NULL;
254 : 1 : GClosure* from_closure = NULL;
255 : :
256 [ + - ]: 1 : if (to_callback)
257 : 1 : to_closure = g_cclosure_new(G_CALLBACK(to_callback), to_data,
258 : : G_CLOSURE_NOTIFY(to_notify));
259 : :
260 [ - + ]: 1 : if (from_callback)
261 : 0 : from_closure = g_cclosure_new(G_CALLBACK(from_callback), from_data,
262 : : G_CLOSURE_NOTIFY(from_notify));
263 : :
264 : 1 : return g_object_bind_property_with_closures(source, source_property, target,
265 : : target_property, flags,
266 : : to_closure, from_closure);
267 : : }
268 : :
269 : : #if GLIB_CHECK_VERSION(2, 72, 0)
270 : 1 : void gjs_g_binding_group_bind_full(
271 : : GBindingGroup* source, const char* source_property, GObject* target,
272 : : const char* target_property, GBindingFlags flags,
273 : : GjsBindingTransformFunc to_callback, void* to_data,
274 : : GDestroyNotify to_notify, GjsBindingTransformFunc from_callback,
275 : : void* from_data, GDestroyNotify from_notify) {
276 : 1 : GClosure* to_closure = NULL;
277 : 1 : GClosure* from_closure = NULL;
278 : :
279 [ + - ]: 1 : if (to_callback)
280 : 1 : to_closure = g_cclosure_new(G_CALLBACK(to_callback), to_data,
281 : : G_CLOSURE_NOTIFY(to_notify));
282 : :
283 [ - + ]: 1 : if (from_callback)
284 : 0 : from_closure = g_cclosure_new(G_CALLBACK(from_callback), from_data,
285 : : G_CLOSURE_NOTIFY(from_notify));
286 : :
287 : 1 : g_binding_group_bind_with_closures(source, source_property, target,
288 : : target_property, flags,
289 : : to_closure, from_closure);
290 : 1 : }
291 : : #endif
292 : :
293 : : #undef G_CLOSURE_NOTIFY
294 : :
295 : 1 : static GParamSpec* gjs_gtk_container_class_find_child_property(
296 : : GIObjectInfo* container_info, GObject* container, const char* property) {
297 : 1 : GIBaseInfo* class_info = NULL;
298 : 1 : GIBaseInfo* find_child_property_fun = NULL;
299 : :
300 : : GIArgument ret;
301 : : GIArgument find_child_property_args[2];
302 : :
303 : 1 : class_info = g_object_info_get_class_struct(container_info);
304 : 1 : find_child_property_fun =
305 : 1 : g_struct_info_find_method(class_info, "find_child_property");
306 : :
307 : 1 : find_child_property_args[0].v_pointer = G_OBJECT_GET_CLASS(container);
308 : 1 : find_child_property_args[1].v_string = (char*)property;
309 : :
310 : 1 : g_function_info_invoke(find_child_property_fun, find_child_property_args, 2,
311 : : NULL, 0, &ret, NULL);
312 : :
313 [ + - ]: 1 : g_clear_pointer(&class_info, g_base_info_unref);
314 [ + - ]: 1 : g_clear_pointer(&find_child_property_fun, g_base_info_unref);
315 : :
316 : 1 : return (GParamSpec*)ret.v_pointer;
317 : : }
318 : :
319 : 1 : void gjs_gtk_container_child_set_property(GObject* container, GObject* child,
320 : : const char* property,
321 : : const GValue* value) {
322 : 1 : GParamSpec* pspec = NULL;
323 : 1 : GIBaseInfo* base_info = NULL;
324 : 1 : GIBaseInfo* child_set_property_fun = NULL;
325 : : GIObjectInfo* container_info;
326 : 1 : GValue value_arg = G_VALUE_INIT;
327 : : GIArgument ret;
328 : :
329 : : GIArgument child_set_property_args[4];
330 : :
331 : 1 : base_info = g_irepository_find_by_name(NULL, "Gtk", "Container");
332 : 1 : container_info = (GIObjectInfo*)base_info;
333 : :
334 : 1 : pspec = gjs_gtk_container_class_find_child_property(container_info,
335 : : container, property);
336 [ - + ]: 1 : if (pspec == NULL) {
337 : 0 : g_warning("%s does not have a property called %s",
338 : : g_type_name(G_OBJECT_TYPE(container)), property);
339 : 0 : goto out;
340 : : }
341 : :
342 [ + - + - ]: 2 : if ((G_VALUE_TYPE(value) == G_TYPE_POINTER) &&
343 [ + - ]: 2 : (g_value_get_pointer(value) == NULL) &&
344 : 1 : !g_value_type_transformable(G_VALUE_TYPE(value), pspec->value_type)) {
345 : : /* Set an empty value. This will happen when we set a NULL value from
346 : : * JS. Since GJS doesn't know the GParamSpec for this property, it will
347 : : * just put NULL into a G_TYPE_POINTER GValue, which will later fail
348 : : * when trying to transform it to the GParamSpec's GType.
349 : : */
350 : 1 : g_value_init(&value_arg, pspec->value_type);
351 : : } else {
352 : 0 : g_value_init(&value_arg, G_VALUE_TYPE(value));
353 : 0 : g_value_copy(value, &value_arg);
354 : : }
355 : :
356 : 1 : child_set_property_fun =
357 : 1 : g_object_info_find_method(container_info, "child_set_property");
358 : :
359 : 1 : child_set_property_args[0].v_pointer = container;
360 : 1 : child_set_property_args[1].v_pointer = child;
361 : 1 : child_set_property_args[2].v_string = (char*)property;
362 : 1 : child_set_property_args[3].v_pointer = &value_arg;
363 : :
364 : 1 : g_function_info_invoke(child_set_property_fun, child_set_property_args, 4,
365 : : NULL, 0, &ret, NULL);
366 : :
367 : 1 : g_value_unset(&value_arg);
368 : :
369 : 1 : out:
370 [ + - ]: 1 : g_clear_pointer(&base_info, g_base_info_unref);
371 [ + - ]: 1 : g_clear_pointer(&child_set_property_fun, g_base_info_unref);
372 : 1 : }
373 : :
374 : : /**
375 : : * gjs_list_store_insert_sorted:
376 : : * @store: a #GListStore
377 : : * @item: the new item
378 : : * @compare_func: (scope call): pairwise comparison function for sorting
379 : : * @user_data: user data for @compare_func
380 : : *
381 : : * Inserts @item into @store at a position to be determined by the
382 : : * @compare_func.
383 : : *
384 : : * The list must already be sorted before calling this function or the
385 : : * result is undefined. Usually you would approach this by only ever
386 : : * inserting items by way of this function.
387 : : *
388 : : * This function takes a ref on @item.
389 : : *
390 : : * Returns: the position at which @item was inserted
391 : : */
392 : 10 : unsigned int gjs_list_store_insert_sorted(GListStore *store, GObject *item,
393 : : GjsCompareDataFunc compare_func,
394 : : void *user_data) {
395 : 10 : return g_list_store_insert_sorted(store, item, (GCompareDataFunc)compare_func, user_data);
396 : : }
397 : :
398 : : /**
399 : : * gjs_list_store_sort:
400 : : * @store: a #GListStore
401 : : * @compare_func: (scope call): pairwise comparison function for sorting
402 : : * @user_data: user data for @compare_func
403 : : *
404 : : * Sort the items in @store according to @compare_func.
405 : : */
406 : 1 : void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func,
407 : : void *user_data) {
408 : 1 : g_list_store_sort(store, (GCompareDataFunc)compare_func, user_data);
409 : 1 : }
410 : :
411 : : /**
412 : : * gjs_gtk_custom_sorter_new:
413 : : * @sort_func: (nullable) (scope call): function to sort items
414 : : * @user_data: user data for @sort_func
415 : : * @destroy: destroy notify for @user_data
416 : : *
417 : : * Creates a new `GtkSorter` that works by calling @sort_func to compare items.
418 : : *
419 : : * If @sort_func is %NULL, all items are considered equal.
420 : : *
421 : : * Returns: (transfer full): a new `GtkCustomSorter`
422 : : */
423 : 2 : GObject* gjs_gtk_custom_sorter_new(GjsCompareDataFunc sort_func,
424 : : void* user_data, GDestroyNotify destroy) {
425 : 2 : GIObjectInfo* container_info =
426 : 2 : g_irepository_find_by_name(NULL, "Gtk", "CustomSorter");
427 : 2 : GIBaseInfo* custom_sorter_new_fun =
428 : 2 : g_object_info_find_method(container_info, "new");
429 : :
430 : : GIArgument ret;
431 : : GIArgument custom_sorter_new_args[3];
432 : 2 : custom_sorter_new_args[0].v_pointer = sort_func;
433 : 2 : custom_sorter_new_args[1].v_pointer = user_data;
434 : 2 : custom_sorter_new_args[2].v_pointer = destroy;
435 : :
436 : 2 : g_function_info_invoke(custom_sorter_new_fun, custom_sorter_new_args, 3,
437 : : NULL, 0, &ret, NULL);
438 : :
439 [ + - ]: 2 : g_clear_pointer(&container_info, g_base_info_unref);
440 [ + - ]: 2 : g_clear_pointer(&custom_sorter_new_fun, g_base_info_unref);
441 : :
442 : 2 : return (GObject*)ret.v_pointer;
443 : : }
444 : :
445 : : /**
446 : : * gjs_gtk_custom_sorter_set_sort_func:
447 : : * @sorter: a `GtkCustomSorter`
448 : : * @sort_func: (nullable) (scope call): function to sort items
449 : : * @user_data: user data to pass to @sort_func
450 : : * @destroy: destroy notify for @user_data
451 : : *
452 : : * Sets (or unsets) the function used for sorting items.
453 : : *
454 : : * If @sort_func is %NULL, all items are considered equal.
455 : : *
456 : : * If the sort func changes its sorting behavior, gtk_sorter_changed() needs to
457 : : * be called.
458 : : *
459 : : * If a previous function was set, its @user_destroy will be called now.
460 : : */
461 : 2 : void gjs_gtk_custom_sorter_set_sort_func(GObject* sorter,
462 : : GjsCompareDataFunc sort_func,
463 : : void* user_data,
464 : : GDestroyNotify destroy) {
465 : 2 : GIObjectInfo* container_info =
466 : 2 : g_irepository_find_by_name(NULL, "Gtk", "CustomSorter");
467 : 2 : GIBaseInfo* set_sort_func_fun =
468 : 2 : g_object_info_find_method(container_info, "set_sort_func");
469 : :
470 : : GIArgument unused_ret;
471 : : GIArgument set_sort_func_args[4];
472 : 2 : set_sort_func_args[0].v_pointer = sorter;
473 : 2 : set_sort_func_args[1].v_pointer = sort_func;
474 : 2 : set_sort_func_args[2].v_pointer = user_data;
475 : 2 : set_sort_func_args[3].v_pointer = destroy;
476 : :
477 : 2 : g_function_info_invoke(set_sort_func_fun, set_sort_func_args, 4, NULL, 0,
478 : : &unused_ret, NULL);
479 : :
480 [ + - ]: 2 : g_clear_pointer(&container_info, g_base_info_unref);
481 [ + - ]: 2 : g_clear_pointer(&set_sort_func_fun, g_base_info_unref);
482 : 2 : }
483 : :
484 : : static bool log_writer_cleared = false;
485 : : static void* log_writer_user_data = NULL;
486 : : static GDestroyNotify log_writer_user_data_free = NULL;
487 : : static GThread* log_writer_thread = NULL;
488 : :
489 : 78 : static GLogWriterOutput gjs_log_writer_func_wrapper(GLogLevelFlags log_level,
490 : : const GLogField* fields,
491 : : size_t n_fields,
492 : : void* user_data) {
493 : 78 : g_assert(log_writer_thread);
494 : :
495 : : // If the log writer function has been cleared with log_set_writer_default()
496 : : // or the wrapper is called from a thread other than the one that set it,
497 : : // return unhandled so the fallback logger is used.
498 [ + + - + ]: 78 : if (log_writer_cleared || g_thread_self() != log_writer_thread)
499 : 4 : return g_log_writer_default(log_level, fields, n_fields, NULL);
500 : :
501 : 74 : GjsGLogWriterFunc func = (GjsGLogWriterFunc)user_data;
502 : : GVariantDict dict;
503 : 74 : g_variant_dict_init(&dict, NULL);
504 : :
505 : : size_t f;
506 [ + + ]: 373 : for (f = 0; f < n_fields; f++) {
507 : 299 : const GLogField* field = &fields[f];
508 : :
509 : : GVariant* value;
510 [ + + ]: 299 : if (field->length < 0) {
511 : 298 : size_t bytes_len = strlen(field->value);
512 : 298 : GBytes* bytes = g_bytes_new(field->value, bytes_len);
513 : :
514 : 298 : value = g_variant_new_maybe(
515 : : G_VARIANT_TYPE_BYTESTRING,
516 : : g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes,
517 : : true));
518 : 298 : g_bytes_unref(bytes);
519 [ + - ]: 1 : } else if (field->length > 0) {
520 : 1 : GBytes* bytes = g_bytes_new(field->value, field->length);
521 : :
522 : 1 : value = g_variant_new_maybe(
523 : : G_VARIANT_TYPE_BYTESTRING,
524 : : g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes,
525 : : true));
526 : 1 : g_bytes_unref(bytes);
527 : : } else {
528 : 0 : value = g_variant_new_maybe(G_VARIANT_TYPE_STRING, NULL);
529 : : }
530 : :
531 : 299 : g_variant_dict_insert_value(&dict, field->key, value);
532 : : }
533 : :
534 : 74 : GVariant* string_fields = g_variant_dict_end(&dict);
535 : 74 : g_variant_ref(string_fields);
536 : :
537 : : GLogWriterOutput output =
538 : 74 : func(log_level, string_fields, log_writer_user_data);
539 : :
540 : 74 : g_variant_unref(string_fields);
541 : :
542 : : // If the function did not handle the log, fallback to the default
543 : : // handler.
544 [ - + ]: 74 : if (output == G_LOG_WRITER_UNHANDLED)
545 : 0 : return g_log_writer_default(log_level, fields, n_fields, NULL);
546 : :
547 : 74 : return output;
548 : : }
549 : :
550 : : /**
551 : : * gjs_log_set_writer_default:
552 : : *
553 : : * Sets the structured logging writer function back to the platform default.
554 : : */
555 : 4 : void gjs_log_set_writer_default() {
556 [ + - ]: 4 : if (log_writer_user_data_free) {
557 : 4 : log_writer_user_data_free(log_writer_user_data);
558 : : }
559 : :
560 : 4 : log_writer_user_data_free = NULL;
561 : 4 : log_writer_user_data = NULL;
562 : 4 : log_writer_thread = g_thread_self();
563 : 4 : log_writer_cleared = true;
564 : 4 : }
565 : :
566 : : /**
567 : : * gjs_log_set_writer_func:
568 : : * @func: (scope notified): callback with log data
569 : : * @user_data: user data for @func
570 : : * @user_data_free: (destroy user_data_free): destroy for @user_data
571 : : *
572 : : * Sets a given function as the writer function for structured logging,
573 : : * passing log fields as a variant. If called from JavaScript the application
574 : : * must call gjs_log_set_writer_default prior to exiting.
575 : : */
576 : 4 : void gjs_log_set_writer_func(GjsGLogWriterFunc func, void* user_data,
577 : : GDestroyNotify user_data_free) {
578 : 4 : log_writer_user_data = user_data;
579 : 4 : log_writer_user_data_free = user_data_free;
580 : 4 : log_writer_thread = g_thread_self();
581 : :
582 : 4 : g_log_set_writer_func(gjs_log_writer_func_wrapper, func, NULL);
583 : 4 : }
584 : :
585 : : /**
586 : : * gjs_clear_terminal:
587 : : *
588 : : * Clears the terminal, if possible.
589 : : */
590 : 2 : void gjs_clear_terminal(void) {
591 [ + - ]: 2 : if (!gjs_console_is_tty(stdout_fd))
592 : 2 : return;
593 : :
594 : 0 : gjs_console_clear();
595 : : }
|