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 <locale.h> /* for setlocale */
10 : : #include <stdbool.h>
11 : : #include <stddef.h> /* for size_t */
12 : : #include <string.h>
13 : :
14 : : #include <glib-object.h>
15 : : #include <girepository.h>
16 : : #include <glib.h>
17 : : #include <glib/gi18n.h> /* for bindtextdomain, bind_textdomain_codeset, textdomain */
18 : :
19 : : #include "libgjs-private/gjs-util.h"
20 : : #include "util/console.h"
21 : :
22 : : char *
23 : 0 : gjs_format_int_alternative_output(int n)
24 : : {
25 : : #ifdef HAVE_PRINTF_ALTERNATIVE_INT
26 : 0 : return g_strdup_printf("%Id", n);
27 : : #else
28 : : return g_strdup_printf("%d", n);
29 : : #endif
30 : : }
31 : :
32 : 5 : GType gjs_locale_category_get_type(void) {
33 : : static size_t gjs_locale_category_get_type = 0;
34 [ + - + - : 5 : if (g_once_init_enter(&gjs_locale_category_get_type)) {
+ - ]
35 : : static const GEnumValue v[] = {
36 : : {GJS_LOCALE_CATEGORY_ALL, "GJS_LOCALE_CATEGORY_ALL", "all"},
37 : : {GJS_LOCALE_CATEGORY_COLLATE, "GJS_LOCALE_CATEGORY_COLLATE",
38 : : "collate"},
39 : : {GJS_LOCALE_CATEGORY_CTYPE, "GJS_LOCALE_CATEGORY_CTYPE", "ctype"},
40 : : {GJS_LOCALE_CATEGORY_MESSAGES, "GJS_LOCALE_CATEGORY_MESSAGES",
41 : : "messages"},
42 : : {GJS_LOCALE_CATEGORY_MONETARY, "GJS_LOCALE_CATEGORY_MONETARY",
43 : : "monetary"},
44 : : {GJS_LOCALE_CATEGORY_NUMERIC, "GJS_LOCALE_CATEGORY_NUMERIC",
45 : : "numeric"},
46 : : {GJS_LOCALE_CATEGORY_TIME, "GJS_LOCALE_CATEGORY_TIME", "time"},
47 : : {0, NULL, NULL}};
48 : 5 : GType g_define_type_id = g_enum_register_static(
49 : : g_intern_static_string("GjsLocaleCategory"), v);
50 : :
51 : 5 : g_once_init_leave(&gjs_locale_category_get_type, g_define_type_id);
52 : : }
53 : 5 : return gjs_locale_category_get_type;
54 : : }
55 : :
56 : : /**
57 : : * gjs_setlocale:
58 : : * @category:
59 : : * @locale: (allow-none):
60 : : *
61 : : * Returns:
62 : : */
63 : : const char *
64 : 4 : gjs_setlocale(GjsLocaleCategory category, const char *locale)
65 : : {
66 : : /* According to man setlocale(3), the return value may be allocated in
67 : : * static storage. */
68 : 4 : return (const char *) setlocale(category, locale);
69 : : }
70 : :
71 : : void
72 : 0 : gjs_textdomain(const char *domain)
73 : : {
74 : 0 : textdomain(domain);
75 : 0 : }
76 : :
77 : : void
78 : 0 : gjs_bindtextdomain(const char *domain,
79 : : const char *location)
80 : : {
81 : 0 : bindtextdomain(domain, location);
82 : : /* Always use UTF-8; we assume it internally here */
83 : 0 : bind_textdomain_codeset(domain, "UTF-8");
84 : 0 : }
85 : :
86 : : GParamFlags
87 : 110 : gjs_param_spec_get_flags(GParamSpec *pspec)
88 : : {
89 : 110 : return pspec->flags;
90 : : }
91 : :
92 : : GType
93 : 2 : gjs_param_spec_get_value_type(GParamSpec *pspec)
94 : : {
95 : 2 : return pspec->value_type;
96 : : }
97 : :
98 : : GType
99 : 0 : gjs_param_spec_get_owner_type(GParamSpec *pspec)
100 : : {
101 : 0 : return pspec->owner_type;
102 : : }
103 : :
104 : : #define G_CLOSURE_NOTIFY(func) ((GClosureNotify)(void (*)(void))func)
105 : :
106 : 1 : GBinding* gjs_g_object_bind_property_full(
107 : : GObject* source, const char* source_property, GObject* target,
108 : : const char* target_property, GBindingFlags flags,
109 : : GjsBindingTransformFunc to_callback, void* to_data,
110 : : GDestroyNotify to_notify, GjsBindingTransformFunc from_callback,
111 : : void* from_data, GDestroyNotify from_notify) {
112 : 1 : GClosure* to_closure = NULL;
113 : 1 : GClosure* from_closure = NULL;
114 : :
115 [ + - ]: 1 : if (to_callback)
116 : 1 : to_closure = g_cclosure_new(G_CALLBACK(to_callback), to_data,
117 : : G_CLOSURE_NOTIFY(to_notify));
118 : :
119 [ - + ]: 1 : if (from_callback)
120 : 0 : from_closure = g_cclosure_new(G_CALLBACK(from_callback), from_data,
121 : : G_CLOSURE_NOTIFY(from_notify));
122 : :
123 : 1 : return g_object_bind_property_with_closures(source, source_property, target,
124 : : target_property, flags,
125 : : to_closure, from_closure);
126 : : }
127 : :
128 : : #if GLIB_CHECK_VERSION(2, 72, 0)
129 : 1 : void gjs_g_binding_group_bind_full(
130 : : GBindingGroup* source, const char* source_property, GObject* target,
131 : : const char* target_property, GBindingFlags flags,
132 : : GjsBindingTransformFunc to_callback, void* to_data,
133 : : GDestroyNotify to_notify, GjsBindingTransformFunc from_callback,
134 : : void* from_data, GDestroyNotify from_notify) {
135 : 1 : GClosure* to_closure = NULL;
136 : 1 : GClosure* from_closure = NULL;
137 : :
138 [ + - ]: 1 : if (to_callback)
139 : 1 : to_closure = g_cclosure_new(G_CALLBACK(to_callback), to_data,
140 : : G_CLOSURE_NOTIFY(to_notify));
141 : :
142 [ - + ]: 1 : if (from_callback)
143 : 0 : from_closure = g_cclosure_new(G_CALLBACK(from_callback), from_data,
144 : : G_CLOSURE_NOTIFY(from_notify));
145 : :
146 : 1 : g_binding_group_bind_with_closures(source, source_property, target,
147 : : target_property, flags,
148 : : to_closure, from_closure);
149 : 1 : }
150 : : #endif
151 : :
152 : : #undef G_CLOSURE_NOTIFY
153 : :
154 : 1 : static GParamSpec* gjs_gtk_container_class_find_child_property(
155 : : GIObjectInfo* container_info, GObject* container, const char* property) {
156 : 1 : GIBaseInfo* class_info = NULL;
157 : 1 : GIBaseInfo* find_child_property_fun = NULL;
158 : :
159 : : GIArgument ret;
160 : : GIArgument find_child_property_args[2];
161 : :
162 : 1 : class_info = g_object_info_get_class_struct(container_info);
163 : 1 : find_child_property_fun =
164 : 1 : g_struct_info_find_method(class_info, "find_child_property");
165 : :
166 : 1 : find_child_property_args[0].v_pointer = G_OBJECT_GET_CLASS(container);
167 : 1 : find_child_property_args[1].v_string = (char*)property;
168 : :
169 : 1 : g_function_info_invoke(find_child_property_fun, find_child_property_args, 2,
170 : : NULL, 0, &ret, NULL);
171 : :
172 [ + - ]: 1 : g_clear_pointer(&class_info, g_base_info_unref);
173 [ + - ]: 1 : g_clear_pointer(&find_child_property_fun, g_base_info_unref);
174 : :
175 : 1 : return (GParamSpec*)ret.v_pointer;
176 : : }
177 : :
178 : 1 : void gjs_gtk_container_child_set_property(GObject* container, GObject* child,
179 : : const char* property,
180 : : const GValue* value) {
181 : 1 : GParamSpec* pspec = NULL;
182 : 1 : GIBaseInfo* base_info = NULL;
183 : 1 : GIBaseInfo* child_set_property_fun = NULL;
184 : : GIObjectInfo* container_info;
185 : 1 : GValue value_arg = G_VALUE_INIT;
186 : : GIArgument ret;
187 : :
188 : : GIArgument child_set_property_args[4];
189 : :
190 : 1 : base_info = g_irepository_find_by_name(NULL, "Gtk", "Container");
191 : 1 : container_info = (GIObjectInfo*)base_info;
192 : :
193 : 1 : pspec = gjs_gtk_container_class_find_child_property(container_info,
194 : : container, property);
195 [ - + ]: 1 : if (pspec == NULL) {
196 : 0 : g_warning("%s does not have a property called %s",
197 : : g_type_name(G_OBJECT_TYPE(container)), property);
198 : 0 : goto out;
199 : : }
200 : :
201 [ + - + - ]: 2 : if ((G_VALUE_TYPE(value) == G_TYPE_POINTER) &&
202 [ + - ]: 2 : (g_value_get_pointer(value) == NULL) &&
203 : 1 : !g_value_type_transformable(G_VALUE_TYPE(value), pspec->value_type)) {
204 : : /* Set an empty value. This will happen when we set a NULL value from
205 : : * JS. Since GJS doesn't know the GParamSpec for this property, it will
206 : : * just put NULL into a G_TYPE_POINTER GValue, which will later fail
207 : : * when trying to transform it to the GParamSpec's GType.
208 : : */
209 : 1 : g_value_init(&value_arg, pspec->value_type);
210 : : } else {
211 : 0 : g_value_init(&value_arg, G_VALUE_TYPE(value));
212 : 0 : g_value_copy(value, &value_arg);
213 : : }
214 : :
215 : 1 : child_set_property_fun =
216 : 1 : g_object_info_find_method(container_info, "child_set_property");
217 : :
218 : 1 : child_set_property_args[0].v_pointer = container;
219 : 1 : child_set_property_args[1].v_pointer = child;
220 : 1 : child_set_property_args[2].v_string = (char*)property;
221 : 1 : child_set_property_args[3].v_pointer = &value_arg;
222 : :
223 : 1 : g_function_info_invoke(child_set_property_fun, child_set_property_args, 4,
224 : : NULL, 0, &ret, NULL);
225 : :
226 : 1 : g_value_unset(&value_arg);
227 : :
228 : 1 : out:
229 [ + - ]: 1 : g_clear_pointer(&base_info, g_base_info_unref);
230 [ + - ]: 1 : g_clear_pointer(&child_set_property_fun, g_base_info_unref);
231 : 1 : }
232 : :
233 : : /**
234 : : * gjs_list_store_insert_sorted:
235 : : * @store: a #GListStore
236 : : * @item: the new item
237 : : * @compare_func: (scope call): pairwise comparison function for sorting
238 : : * @user_data: (closure): user data for @compare_func
239 : : *
240 : : * Inserts @item into @store at a position to be determined by the
241 : : * @compare_func.
242 : : *
243 : : * The list must already be sorted before calling this function or the
244 : : * result is undefined. Usually you would approach this by only ever
245 : : * inserting items by way of this function.
246 : : *
247 : : * This function takes a ref on @item.
248 : : *
249 : : * Returns: the position at which @item was inserted
250 : : */
251 : 10 : unsigned int gjs_list_store_insert_sorted(GListStore *store, GObject *item,
252 : : GjsCompareDataFunc compare_func,
253 : : void *user_data) {
254 : 10 : return g_list_store_insert_sorted(store, item, (GCompareDataFunc)compare_func, user_data);
255 : : }
256 : :
257 : : /**
258 : : * gjs_list_store_sort:
259 : : * @store: a #GListStore
260 : : * @compare_func: (scope call): pairwise comparison function for sorting
261 : : * @user_data: (closure): user data for @compare_func
262 : : *
263 : : * Sort the items in @store according to @compare_func.
264 : : */
265 : 1 : void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func,
266 : : void *user_data) {
267 : 1 : g_list_store_sort(store, (GCompareDataFunc)compare_func, user_data);
268 : 1 : }
269 : :
270 : : /**
271 : : * gjs_gtk_custom_sorter_new:
272 : : * @sort_func: (nullable) (scope call): function to sort items
273 : : * @user_data: (closure): user data for @compare_func
274 : : * @destroy: destroy notify for @user_data
275 : : *
276 : : * Creates a new `GtkSorter` that works by calling @sort_func to compare items.
277 : : *
278 : : * If @sort_func is %NULL, all items are considered equal.
279 : : *
280 : : * Returns: (transfer full): a new `GtkCustomSorter`
281 : : */
282 : 2 : GObject* gjs_gtk_custom_sorter_new(GjsCompareDataFunc sort_func,
283 : : void* user_data, GDestroyNotify destroy) {
284 : 2 : GIObjectInfo* container_info =
285 : 2 : g_irepository_find_by_name(NULL, "Gtk", "CustomSorter");
286 : 2 : GIBaseInfo* custom_sorter_new_fun =
287 : 2 : g_object_info_find_method(container_info, "new");
288 : :
289 : : GIArgument ret;
290 : : GIArgument custom_sorter_new_args[3];
291 : 2 : custom_sorter_new_args[0].v_pointer = sort_func;
292 : 2 : custom_sorter_new_args[1].v_pointer = user_data;
293 : 2 : custom_sorter_new_args[2].v_pointer = destroy;
294 : :
295 : 2 : g_function_info_invoke(custom_sorter_new_fun, custom_sorter_new_args, 3,
296 : : NULL, 0, &ret, NULL);
297 : :
298 [ + - ]: 2 : g_clear_pointer(&container_info, g_base_info_unref);
299 [ + - ]: 2 : g_clear_pointer(&custom_sorter_new_fun, g_base_info_unref);
300 : :
301 : 2 : return (GObject*)ret.v_pointer;
302 : : }
303 : :
304 : : /**
305 : : * gjs_gtk_custom_sorter_set_sort_func:
306 : : * @sorter: a `GtkCustomSorter`
307 : : * @sort_func: (nullable) (scope call): function to sort items
308 : : * @user_data: (closure): user data to pass to @sort_func
309 : : * @destroy: destroy notify for @user_data
310 : : *
311 : : * Sets (or unsets) the function used for sorting items.
312 : : *
313 : : * If @sort_func is %NULL, all items are considered equal.
314 : : *
315 : : * If the sort func changes its sorting behavior, gtk_sorter_changed() needs to
316 : : * be called.
317 : : *
318 : : * If a previous function was set, its @user_destroy will be called now.
319 : : */
320 : 2 : void gjs_gtk_custom_sorter_set_sort_func(GObject* sorter,
321 : : GjsCompareDataFunc sort_func,
322 : : void* user_data,
323 : : GDestroyNotify destroy) {
324 : 2 : GIObjectInfo* container_info =
325 : 2 : g_irepository_find_by_name(NULL, "Gtk", "CustomSorter");
326 : 2 : GIBaseInfo* set_sort_func_fun =
327 : 2 : g_object_info_find_method(container_info, "set_sort_func");
328 : :
329 : : GIArgument unused_ret;
330 : : GIArgument set_sort_func_args[4];
331 : 2 : set_sort_func_args[0].v_pointer = sorter;
332 : 2 : set_sort_func_args[1].v_pointer = sort_func;
333 : 2 : set_sort_func_args[2].v_pointer = user_data;
334 : 2 : set_sort_func_args[3].v_pointer = destroy;
335 : :
336 : 2 : g_function_info_invoke(set_sort_func_fun, set_sort_func_args, 4, NULL, 0,
337 : : &unused_ret, NULL);
338 : :
339 [ + - ]: 2 : g_clear_pointer(&container_info, g_base_info_unref);
340 [ + - ]: 2 : g_clear_pointer(&set_sort_func_fun, g_base_info_unref);
341 : 2 : }
342 : :
343 : : static void* log_writer_user_data = NULL;
344 : : static GDestroyNotify log_writer_user_data_free = NULL;
345 : : static GThread* log_writer_thread = NULL;
346 : :
347 : 83 : static GLogWriterOutput gjs_log_writer_func_wrapper(GLogLevelFlags log_level,
348 : : const GLogField* fields,
349 : : size_t n_fields,
350 : : void* user_data) {
351 : 83 : g_assert(log_writer_thread);
352 : :
353 : : // If the wrapper is called from a thread other than the one that set it,
354 : : // return unhandled so the fallback logger is used.
355 [ - + ]: 83 : if (g_thread_self() != log_writer_thread)
356 : 0 : return g_log_writer_default(log_level, fields, n_fields, NULL);
357 : :
358 : 83 : GjsGLogWriterFunc func = (GjsGLogWriterFunc)user_data;
359 : : GVariantDict dict;
360 : 83 : g_variant_dict_init(&dict, NULL);
361 : :
362 : : size_t f;
363 [ + + ]: 418 : for (f = 0; f < n_fields; f++) {
364 : 335 : const GLogField* field = &fields[f];
365 : :
366 : : GVariant* value;
367 [ + + ]: 335 : if (field->length < 0) {
368 : 334 : size_t bytes_len = strlen(field->value);
369 : 334 : GBytes* bytes = g_bytes_new(field->value, bytes_len);
370 : :
371 : 334 : value = g_variant_new_maybe(
372 : : G_VARIANT_TYPE_BYTESTRING,
373 : : g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes,
374 : : true));
375 : 334 : g_bytes_unref(bytes);
376 [ + - ]: 1 : } else if (field->length > 0) {
377 : 1 : GBytes* bytes = g_bytes_new(field->value, field->length);
378 : :
379 : 1 : value = g_variant_new_maybe(
380 : : G_VARIANT_TYPE_BYTESTRING,
381 : : g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes,
382 : : true));
383 : 1 : g_bytes_unref(bytes);
384 : : } else {
385 : 0 : value = g_variant_new_maybe(G_VARIANT_TYPE_STRING, NULL);
386 : : }
387 : :
388 : 335 : g_variant_dict_insert_value(&dict, field->key, value);
389 : : }
390 : :
391 : 83 : GVariant* string_fields = g_variant_dict_end(&dict);
392 : 83 : g_variant_ref(string_fields);
393 : :
394 : : GLogWriterOutput output =
395 : 83 : func(log_level, string_fields, log_writer_user_data);
396 : :
397 : 83 : g_variant_unref(string_fields);
398 : :
399 : : // If the function did not handle the log, fallback to the default
400 : : // handler.
401 [ + + ]: 83 : if (output == G_LOG_WRITER_UNHANDLED)
402 : 6 : return g_log_writer_default(log_level, fields, n_fields, NULL);
403 : :
404 : 77 : return output;
405 : : }
406 : :
407 : : /**
408 : : * gjs_log_set_writer_default:
409 : : *
410 : : * Sets the structured logging writer function back to the platform default.
411 : : */
412 : 0 : void gjs_log_set_writer_default() {
413 [ # # ]: 0 : if (log_writer_user_data_free) {
414 : 0 : log_writer_user_data_free(log_writer_user_data);
415 : : }
416 : :
417 : 0 : g_log_set_writer_func(g_log_writer_default, NULL, NULL);
418 : 0 : log_writer_user_data_free = NULL;
419 : 0 : log_writer_user_data = NULL;
420 : 0 : log_writer_thread = NULL;
421 : 0 : }
422 : :
423 : : /**
424 : : * gjs_log_set_writer_func:
425 : : * @func: (scope notified): callback with log data
426 : : * @user_data: (closure): user data for @func
427 : : * @user_data_free: (destroy user_data_free): destroy for @user_data
428 : : *
429 : : * Sets a given function as the writer function for structured logging,
430 : : * passing log fields as a variant. If called from JavaScript the application
431 : : * must call gjs_log_set_writer_default prior to exiting.
432 : : */
433 : 4 : void gjs_log_set_writer_func(GjsGLogWriterFunc func, void* user_data,
434 : : GDestroyNotify user_data_free) {
435 : 4 : log_writer_user_data = user_data;
436 : 4 : log_writer_user_data_free = user_data_free;
437 : 4 : log_writer_thread = g_thread_self();
438 : :
439 : 4 : g_log_set_writer_func(gjs_log_writer_func_wrapper, func, NULL);
440 : 4 : }
441 : :
442 : : /**
443 : : * gjs_clear_terminal:
444 : : *
445 : : * Clears the terminal, if possible.
446 : : */
447 : 2 : void gjs_clear_terminal(void) {
448 [ + - ]: 2 : if (!gjs_console_is_tty(stdout_fd))
449 : 2 : return;
450 : :
451 : 0 : gjs_console_clear();
452 : : }
|