LCOV - code coverage report
Current view: top level - libgjs-private - gjs-util.c (source / functions) Coverage Total Hit
Test: gjs- Code Coverage Lines: 79.6 % 245 195
Test Date: 2025-04-13 21:33:23 Functions: 84.6 % 26 22
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: 53.1 % 96 51

             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                 :             : }
        

Generated by: LCOV version 2.0-1