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