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 *
gjs_format_int_alternative_output(int n)23 gjs_format_int_alternative_output(int n)
24 {
25 #ifdef HAVE_PRINTF_ALTERNATIVE_INT
26     return g_strdup_printf("%Id", n);
27 #else
28     return g_strdup_printf("%d", n);
29 #endif
30 }
31 
gjs_locale_category_get_type(void)32 GType gjs_locale_category_get_type(void) {
33     static size_t gjs_locale_category_get_type = 0;
34     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         GType g_define_type_id = g_enum_register_static(
49             g_intern_static_string("GjsLocaleCategory"), v);
50 
51         g_once_init_leave(&gjs_locale_category_get_type, g_define_type_id);
52     }
53     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 *
gjs_setlocale(GjsLocaleCategory category,const char * locale)64 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     return (const char *) setlocale(category, locale);
69 }
70 
71 void
gjs_textdomain(const char * domain)72 gjs_textdomain(const char *domain)
73 {
74     textdomain(domain);
75 }
76 
77 void
gjs_bindtextdomain(const char * domain,const char * location)78 gjs_bindtextdomain(const char *domain,
79                    const char *location)
80 {
81     bindtextdomain(domain, location);
82     /* Always use UTF-8; we assume it internally here */
83     bind_textdomain_codeset(domain, "UTF-8");
84 }
85 
86 GParamFlags
gjs_param_spec_get_flags(GParamSpec * pspec)87 gjs_param_spec_get_flags(GParamSpec *pspec)
88 {
89     return pspec->flags;
90 }
91 
92 GType
gjs_param_spec_get_value_type(GParamSpec * pspec)93 gjs_param_spec_get_value_type(GParamSpec *pspec)
94 {
95     return pspec->value_type;
96 }
97 
98 GType
gjs_param_spec_get_owner_type(GParamSpec * pspec)99 gjs_param_spec_get_owner_type(GParamSpec *pspec)
100 {
101     return pspec->owner_type;
102 }
103 
104 #define G_CLOSURE_NOTIFY(func) ((GClosureNotify)(void (*)(void))func)
105 
gjs_g_object_bind_property_full(GObject * source,const char * source_property,GObject * target,const char * target_property,GBindingFlags flags,GjsBindingTransformFunc to_callback,void * to_data,GDestroyNotify to_notify,GjsBindingTransformFunc from_callback,void * from_data,GDestroyNotify from_notify)106 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     GClosure* to_closure = NULL;
113     GClosure* from_closure = NULL;
114 
115     if (to_callback)
116         to_closure = g_cclosure_new(G_CALLBACK(to_callback), to_data,
117                                     G_CLOSURE_NOTIFY(to_notify));
118 
119     if (from_callback)
120         from_closure = g_cclosure_new(G_CALLBACK(from_callback), from_data,
121                                       G_CLOSURE_NOTIFY(from_notify));
122 
123     return g_object_bind_property_with_closures(source, source_property, target,
124                                                 target_property, flags,
125                                                 to_closure, from_closure);
126 }
127 
128 #undef G_CLOSURE_NOTIFY
129 
gjs_gtk_container_class_find_child_property(GIObjectInfo * container_info,GObject * container,const char * property)130 static GParamSpec* gjs_gtk_container_class_find_child_property(
131     GIObjectInfo* container_info, GObject* container, const char* property) {
132     GIBaseInfo* class_info = NULL;
133     GIBaseInfo* find_child_property_fun = NULL;
134 
135     GIArgument ret;
136     GIArgument find_child_property_args[2];
137 
138     class_info = g_object_info_get_class_struct(container_info);
139     find_child_property_fun =
140         g_struct_info_find_method(class_info, "find_child_property");
141 
142     find_child_property_args[0].v_pointer = G_OBJECT_GET_CLASS(container);
143     find_child_property_args[1].v_string = (char*)property;
144 
145     g_function_info_invoke(find_child_property_fun, find_child_property_args, 2,
146                            NULL, 0, &ret, NULL);
147 
148     g_clear_pointer(&class_info, g_base_info_unref);
149     g_clear_pointer(&find_child_property_fun, g_base_info_unref);
150 
151     return (GParamSpec*)ret.v_pointer;
152 }
153 
gjs_gtk_container_child_set_property(GObject * container,GObject * child,const char * property,const GValue * value)154 void gjs_gtk_container_child_set_property(GObject* container, GObject* child,
155                                           const char* property,
156                                           const GValue* value) {
157     GParamSpec* pspec = NULL;
158     GIBaseInfo* base_info = NULL;
159     GIBaseInfo* child_set_property_fun = NULL;
160     GIObjectInfo* container_info;
161     GValue value_arg = G_VALUE_INIT;
162     GIArgument ret;
163 
164     GIArgument child_set_property_args[4];
165 
166     base_info = g_irepository_find_by_name(NULL, "Gtk", "Container");
167     container_info = (GIObjectInfo*)base_info;
168 
169     pspec = gjs_gtk_container_class_find_child_property(container_info,
170                                                         container, property);
171     if (pspec == NULL) {
172         g_warning("%s does not have a property called %s",
173                   g_type_name(G_OBJECT_TYPE(container)), property);
174         goto out;
175     }
176 
177     if ((G_VALUE_TYPE(value) == G_TYPE_POINTER) &&
178         (g_value_get_pointer(value) == NULL) &&
179         !g_value_type_transformable(G_VALUE_TYPE(value), pspec->value_type)) {
180         /* Set an empty value. This will happen when we set a NULL value from
181          * JS. Since GJS doesn't know the GParamSpec for this property, it will
182          * just put NULL into a G_TYPE_POINTER GValue, which will later fail
183          * when trying to transform it to the GParamSpec's GType.
184          */
185         g_value_init(&value_arg, pspec->value_type);
186     } else {
187         g_value_init(&value_arg, G_VALUE_TYPE(value));
188         g_value_copy(value, &value_arg);
189     }
190 
191     child_set_property_fun =
192         g_object_info_find_method(container_info, "child_set_property");
193 
194     child_set_property_args[0].v_pointer = container;
195     child_set_property_args[1].v_pointer = child;
196     child_set_property_args[2].v_string = (char*)property;
197     child_set_property_args[3].v_pointer = &value_arg;
198 
199     g_function_info_invoke(child_set_property_fun, child_set_property_args, 4,
200                            NULL, 0, &ret, NULL);
201 
202     g_value_unset(&value_arg);
203 
204 out:
205     g_clear_pointer(&base_info, g_base_info_unref);
206     g_clear_pointer(&child_set_property_fun, g_base_info_unref);
207 }
208 
209 /**
210  * gjs_list_store_insert_sorted:
211  * @store: a #GListStore
212  * @item: the new item
213  * @compare_func: (scope call): pairwise comparison function for sorting
214  * @user_data: (closure): user data for @compare_func
215  *
216  * Inserts @item into @store at a position to be determined by the
217  * @compare_func.
218  *
219  * The list must already be sorted before calling this function or the
220  * result is undefined.  Usually you would approach this by only ever
221  * inserting items by way of this function.
222  *
223  * This function takes a ref on @item.
224  *
225  * Returns: the position at which @item was inserted
226  */
gjs_list_store_insert_sorted(GListStore * store,GObject * item,GjsCompareDataFunc compare_func,void * user_data)227 unsigned int gjs_list_store_insert_sorted(GListStore *store, GObject *item,
228                                           GjsCompareDataFunc compare_func,
229                                           void *user_data) {
230   return g_list_store_insert_sorted(store, item, (GCompareDataFunc)compare_func, user_data);
231 }
232 
233 /**
234  * gjs_list_store_sort:
235  * @store: a #GListStore
236  * @compare_func: (scope call): pairwise comparison function for sorting
237  * @user_data: (closure): user data for @compare_func
238  *
239  * Sort the items in @store according to @compare_func.
240  */
gjs_list_store_sort(GListStore * store,GjsCompareDataFunc compare_func,void * user_data)241 void gjs_list_store_sort(GListStore *store, GjsCompareDataFunc compare_func,
242                          void *user_data) {
243   g_list_store_sort(store, (GCompareDataFunc)compare_func, user_data);
244 }
245 
246 typedef struct WriterFuncData {
247     GjsGLogWriterFunc func;
248     void* wrapped_user_data;
249     GDestroyNotify wrapped_user_data_free;
250 } WriterFuncData;
251 
252 static void* log_writer_user_data = NULL;
253 static GDestroyNotify log_writer_user_data_free = NULL;
254 
gjs_log_writer_func_wrapper(GLogLevelFlags log_level,const GLogField * fields,size_t n_fields,void * user_data)255 GLogWriterOutput gjs_log_writer_func_wrapper(GLogLevelFlags log_level,
256                                              const GLogField* fields,
257                                              size_t n_fields, void* user_data) {
258     GjsGLogWriterFunc func = (GjsGLogWriterFunc)user_data;
259     GVariantDict dict;
260     g_variant_dict_init(&dict, NULL);
261 
262     size_t f;
263     for (f = 0; f < n_fields; f++) {
264         const GLogField* field = &fields[f];
265 
266         GVariant* value;
267         if (field->length < 0) {
268             size_t bytes_len = strlen(field->value);
269             GBytes* bytes = g_bytes_new(field->value, bytes_len);
270 
271             value = g_variant_new_maybe(
272                 G_VARIANT_TYPE_BYTESTRING,
273                 g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes,
274                                          true));
275             g_bytes_unref(bytes);
276         } else if (field->length > 0) {
277             GBytes* bytes = g_bytes_new(field->value, field->length);
278 
279             value = g_variant_new_maybe(
280                 G_VARIANT_TYPE_BYTESTRING,
281                 g_variant_new_from_bytes(G_VARIANT_TYPE_BYTESTRING, bytes,
282                                          true));
283             g_bytes_unref(bytes);
284         } else {
285             value = g_variant_new_maybe(G_VARIANT_TYPE_STRING, NULL);
286         }
287 
288         g_variant_dict_insert_value(&dict, field->key, value);
289     }
290 
291     GVariant* string_fields = g_variant_dict_end(&dict);
292     g_variant_ref(string_fields);
293 
294     GLogWriterOutput output =
295         func(log_level, string_fields, log_writer_user_data);
296 
297     g_variant_unref(string_fields);
298     return output;
299 }
300 
301 /**
302  * gjs_log_set_writer_default:
303  *
304  * Sets the structured logging writer function back to the platform default.
305  */
gjs_log_set_writer_default()306 void gjs_log_set_writer_default() {
307     if (log_writer_user_data_free) {
308         log_writer_user_data_free(log_writer_user_data);
309     }
310 
311     g_log_set_writer_func(g_log_writer_default, NULL, NULL);
312     log_writer_user_data_free = NULL;
313     log_writer_user_data = NULL;
314 }
315 
316 /**
317  * gjs_log_set_writer_func:
318  * @func: (scope notified): callback with log data
319  * @user_data: (closure): user data for @func
320  * @user_data_free: (destroy user_data_free): destroy for @user_data
321  *
322  * Sets a given function as the writer function for structured logging,
323  * passing log fields as a variant. If called from JavaScript the application
324  * must call gjs_log_set_writer_default prior to exiting.
325  */
gjs_log_set_writer_func(GjsGLogWriterFunc func,void * user_data,GDestroyNotify user_data_free)326 void gjs_log_set_writer_func(GjsGLogWriterFunc func, void* user_data,
327                              GDestroyNotify user_data_free) {
328     log_writer_user_data = user_data;
329     log_writer_user_data_free = user_data_free;
330 
331     g_log_set_writer_func(gjs_log_writer_func_wrapper, func, NULL);
332 }
333 
334 /**
335  * gjs_clear_terminal:
336  *
337  * Clears the terminal, if possible.
338  */
gjs_clear_terminal(void)339 void gjs_clear_terminal(void) {
340     if (!gjs_console_is_tty(stdout_fd))
341         return;
342 
343     gjs_console_clear();
344 }
345