/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ #include "config.h" #include #include #include "shell-util.h" #include #include #include #ifdef HAVE__NL_TIME_FIRST_WEEKDAY #include #endif #ifdef WITH_SYSTEMD #include #include #endif static void stop_pick (ClutterActor *actor, const ClutterColor *color) { g_signal_stop_emission_by_name (actor, "pick"); } /** * shell_util_set_hidden_from_pick: * @actor: A #ClutterActor * @hidden: Whether @actor should be hidden from pick * * If @hidden is %TRUE, hide @actor from pick even with a mode of * %CLUTTER_PICK_ALL; if @hidden is %FALSE, unhide @actor. */ void shell_util_set_hidden_from_pick (ClutterActor *actor, gboolean hidden) { gpointer existing_handler_data; existing_handler_data = g_object_get_data (G_OBJECT (actor), "shell-stop-pick"); if (hidden) { if (existing_handler_data != NULL) return; g_signal_connect (actor, "pick", G_CALLBACK (stop_pick), NULL); g_object_set_data (G_OBJECT (actor), "shell-stop-pick", GUINT_TO_POINTER (1)); } else { if (existing_handler_data == NULL) return; g_signal_handlers_disconnect_by_func (actor, stop_pick, NULL); g_object_set_data (G_OBJECT (actor), "shell-stop-pick", NULL); } } /** * shell_util_get_transformed_allocation: * @actor: a #ClutterActor * @box: (out): location to store returned box in stage coordinates * * This function is similar to a combination of clutter_actor_get_transformed_position(), * and clutter_actor_get_transformed_size(), but unlike * clutter_actor_get_transformed_size(), it always returns a transform * of the current allocation, while clutter_actor_get_transformed_size() returns * bad values (the transform of the requested size) if a relayout has been * queued. * * This function is more convenient to use than * clutter_actor_get_abs_allocation_vertices() if no transformation is in effect * and also works around limitations in the GJS binding of arrays. */ void shell_util_get_transformed_allocation (ClutterActor *actor, ClutterActorBox *box) { /* Code adapted from clutter-actor.c: * Copyright 2006, 2007, 2008 OpenedHand Ltd */ ClutterVertex v[4]; gfloat x_min, x_max, y_min, y_max; gint i; g_return_if_fail (CLUTTER_IS_ACTOR (actor)); clutter_actor_get_abs_allocation_vertices (actor, v); x_min = x_max = v[0].x; y_min = y_max = v[0].y; for (i = 1; i < G_N_ELEMENTS (v); ++i) { if (v[i].x < x_min) x_min = v[i].x; if (v[i].x > x_max) x_max = v[i].x; if (v[i].y < y_min) y_min = v[i].y; if (v[i].y > y_max) y_max = v[i].y; } box->x1 = x_min; box->y1 = y_min; box->x2 = x_max; box->y2 = y_max; } char * shell_util_normalize_and_casefold (const char *str) { char *normalized, *result; if (str == NULL) return NULL; /* NOTE: 'ALL' is equivalent to 'NFKD'. If this is ever updated, please * update the unaccenting mechanism as well. */ normalized = g_utf8_normalize (str, -1, G_NORMALIZE_ALL); result = g_utf8_casefold (normalized, -1); g_free (normalized); return result; } /* Combining diacritical mark? * Basic range: [0x0300,0x036F] * Supplement: [0x1DC0,0x1DFF] * For Symbols: [0x20D0,0x20FF] * Half marks: [0xFE20,0xFE2F] */ #define IS_CDM_UCS4(c) (((c) >= 0x0300 && (c) <= 0x036F) || \ ((c) >= 0x1DC0 && (c) <= 0x1DFF) || \ ((c) >= 0x20D0 && (c) <= 0x20FF) || \ ((c) >= 0xFE20 && (c) <= 0xFE2F)) /* Copied from tracker/src/libtracker-fts/tracker-parser-glib.c under the GPL * Originally written by Aleksander Morgado */ char * shell_util_normalize_casefold_and_unaccent (const char *str) { char *tmp; gsize i = 0, j = 0, ilen; if (str == NULL) return NULL; /* Get the NFKD-normalized and casefolded string */ tmp = shell_util_normalize_and_casefold (str); ilen = strlen (tmp); while (i < ilen) { gunichar unichar; gchar *next_utf8; gint utf8_len; /* Get next character of the word as UCS4 */ unichar = g_utf8_get_char_validated (&tmp[i], -1); /* Invalid UTF-8 character or end of original string. */ if (unichar == (gunichar) -1 || unichar == (gunichar) -2) { break; } /* Find next UTF-8 character */ next_utf8 = g_utf8_next_char (&tmp[i]); utf8_len = next_utf8 - &tmp[i]; if (IS_CDM_UCS4 ((guint32) unichar)) { /* If the given unichar is a combining diacritical mark, * just update the original index, not the output one */ i += utf8_len; continue; } /* If already found a previous combining * diacritical mark, indexes are different so * need to copy characters. As output and input * buffers may overlap, need to use memmove * instead of memcpy */ if (i != j) { memmove (&tmp[j], &tmp[i], utf8_len); } /* Update both indexes */ i += utf8_len; j += utf8_len; } /* Force proper string end */ tmp[j] = '\0'; return tmp; } /** * shell_util_format_date: * @format: a strftime-style string format, as parsed by * g_date_time_format() * @time_ms: milliseconds since 1970-01-01 00:00:00 UTC; the * value returned by Date.getTime() * * Formats a date for the current locale. This should be * used instead of the Spidermonkey Date.toLocaleFormat() * extension because Date.toLocaleFormat() is buggy for * Unicode format strings: * https://bugzilla.mozilla.org/show_bug.cgi?id=508783 * * Return value: the formatted date. If the date is * outside of the range of a GDateTime (which contains * any plausible dates we actually care about), will * return an empty string. */ char * shell_util_format_date (const char *format, gint64 time_ms) { GDateTime *datetime; GTimeVal tv; char *result; tv.tv_sec = time_ms / 1000; tv.tv_usec = (time_ms % 1000) * 1000; datetime = g_date_time_new_from_timeval_local (&tv); if (!datetime) /* time_ms is out of range of GDateTime */ return g_strdup (""); result = g_date_time_format (datetime, format); g_date_time_unref (datetime); return result; } /** * shell_util_get_week_start: * * Gets the first week day for the current locale, expressed as a * number in the range 0..6, representing week days from Sunday to * Saturday. * * Returns: A number representing the first week day for the current * locale */ /* Copied from gtkcalendar.c */ int shell_util_get_week_start () { int week_start; #ifdef HAVE__NL_TIME_FIRST_WEEKDAY union { unsigned int word; char *string; } langinfo; int week_1stday = 0; int first_weekday = 1; guint week_origin; #else char *gtk_week_start; #endif #ifdef HAVE__NL_TIME_FIRST_WEEKDAY langinfo.string = nl_langinfo (_NL_TIME_FIRST_WEEKDAY); first_weekday = langinfo.string[0]; langinfo.string = nl_langinfo (_NL_TIME_WEEK_1STDAY); week_origin = langinfo.word; if (week_origin == 19971130) /* Sunday */ week_1stday = 0; else if (week_origin == 19971201) /* Monday */ week_1stday = 1; else g_warning ("Unknown value of _NL_TIME_WEEK_1STDAY.\n"); week_start = (week_1stday + first_weekday - 1) % 7; #else /* Use a define to hide the string from xgettext */ # define GTK_WEEK_START "calendar:week_start:0" gtk_week_start = dgettext ("gtk30", GTK_WEEK_START); if (strncmp (gtk_week_start, "calendar:week_start:", 20) == 0) week_start = *(gtk_week_start + 20) - '0'; else week_start = -1; if (week_start < 0 || week_start > 6) { g_warning ("Whoever translated calendar:week_start:0 for GTK+ " "did so wrongly.\n"); week_start = 0; } #endif return week_start; } /** * shell_write_string_to_stream: * @stream: a #GOutputStream * @str: a UTF-8 string to write to @stream * @error: location to store GError * * Write a string to a GOutputStream as UTF-8. This is a workaround * for not having binary buffers in GJS. * * Return value: %TRUE if write succeeded */ gboolean shell_write_string_to_stream (GOutputStream *stream, const char *str, GError **error) { return g_output_stream_write_all (stream, str, strlen (str), NULL, NULL, error); } /** * shell_get_file_contents_utf8_sync: * @path: UTF-8 encoded filename path * @error: a #GError * * Synchronously load the contents of a file as a NUL terminated * string, validating it as UTF-8. Embedded NUL characters count as * invalid content. * * Returns: (transfer full): File contents */ char * shell_get_file_contents_utf8_sync (const char *path, GError **error) { char *contents; gsize len; if (!g_file_get_contents (path, &contents, &len, error)) return NULL; if (!g_utf8_validate (contents, len, NULL)) { g_free (contents); g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, "File %s contains invalid UTF-8", path); return NULL; } return contents; } /** * shell_session_is_active_for_systemd: * * Checks whether the session we are running in is currently active, * i.e. in the foreground and ready for user input. * * Returns: TRUE if session is active */ gboolean shell_session_is_active_for_systemd (void) { /* If this isn't systemd, let's assume the session is active. */ #ifdef WITH_SYSTEMD if (sd_booted () <= 0) return TRUE; return sd_session_is_active (NULL) != 0; #else return TRUE; #endif } /** * shell_util_wifexited: * @status: the status returned by wait() or waitpid() * @exit: (out): the actual exit status of the process * * Implements libc standard WIFEXITED, that cannot be used JS * code. * Returns: TRUE if the process exited normally, FALSE otherwise */ gboolean shell_util_wifexited (int status, int *exit) { gboolean ret; ret = WIFEXITED(status); if (ret) *exit = WEXITSTATUS(status); return ret; } /** * shell_util_create_pixbuf_from_data: * @data: (array length=len) (element-type guint8) (transfer full): * @len: * @colorspace: * @has_alpha: * @bits_per_sample: * @width: * @height: * @rowstride: * * Workaround for non-introspectability of gdk_pixbuf_from_data(). * * Returns: (transfer full): */ GdkPixbuf * shell_util_create_pixbuf_from_data (const guchar *data, gsize len, GdkColorspace colorspace, gboolean has_alpha, int bits_per_sample, int width, int height, int rowstride) { return gdk_pixbuf_new_from_data (data, colorspace, has_alpha, bits_per_sample, width, height, rowstride, (GdkPixbufDestroyNotify) g_free, NULL); }