meson: Only perform macos checks on macos
[geany-mirror.git] / src / utils.c
blobc8f264d0da3bdfb16e437032c05a2d85c68d51dc
1 /*
2 * utils.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * General utility functions, non-GTK related.
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 #include "utils.h"
31 #include "app.h"
32 #include "dialogs.h"
33 #include "document.h"
34 #include "prefs.h"
35 #include "prefix.h"
36 #include "sciwrappers.h"
37 #include "spawn.h"
38 #include "support.h"
39 #include "tm_source_file.h" // for tm_get_real_path()
40 #include "templates.h"
41 #include "ui_utils.h"
42 #include "win32.h"
43 #include "osx.h"
45 #include <stdlib.h>
46 #include <ctype.h>
47 #include <math.h>
48 #include <unistd.h>
49 #include <string.h>
50 #include <errno.h>
51 #include <stdarg.h>
53 #ifdef HAVE_SYS_STAT_H
54 # include <sys/stat.h>
55 #endif
56 #ifdef HAVE_SYS_TYPES_H
57 # include <sys/types.h>
58 #endif
60 #include <glib/gstdio.h>
61 #include <gio/gio.h>
64 /**
65 * Tries to open the given URI in a browser.
66 * On Windows, the system's default browser is opened.
67 * On non-Windows systems, the browser command can be set in the preferences dialog.
68 * If unset (empty) the system's default browser is used. If set it specifies the
69 * command to execute. Either way, if it fails the user is prompted to correct the
70 * pref.
72 * @param uri The URI to open in the web browser.
74 * @since 0.16
75 **/
76 GEANY_API_SYMBOL
77 void utils_open_browser(const gchar *uri)
79 #ifdef G_OS_WIN32
80 g_return_if_fail(uri != NULL);
81 win32_open_browser(uri);
82 #else
83 gchar *new_cmd, *argv[2] = { (gchar *) uri, NULL };
85 g_return_if_fail(uri != NULL);
87 while (1)
89 /* Uses the user's default browser akin to xdg-open (in flatpak through a portal) */
90 if (EMPTY(tool_prefs.browser_cmd))
92 if (gtk_show_uri_on_window(GTK_WINDOW(main_widgets.window), uri, GDK_CURRENT_TIME, NULL))
93 break;
95 else if (spawn_async(NULL, tool_prefs.browser_cmd, argv, NULL, NULL, NULL))
96 break;
98 /* Allow the user to correct the pref. new_cmd may become empty. */
99 new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window),
100 _("Failed to spawn the configured browser command. Please "
101 "enter a valid command or leave it empty in order "
102 "to spawn the system default browser."),
103 tool_prefs.browser_cmd);
105 if (new_cmd == NULL) /* user canceled */
106 break;
108 SETPTR(tool_prefs.browser_cmd, new_cmd);
110 #endif
114 /* taken from anjuta, to determine the EOL mode of the file */
115 gint utils_get_line_endings(const gchar* buffer, gsize size)
117 gsize i;
118 guint cr, lf, crlf, max_mode;
119 gint mode;
121 cr = lf = crlf = 0;
123 for (i = 0; i < size ; i++)
125 if (buffer[i] == 0x0a)
127 /* LF */
128 lf++;
130 else if (buffer[i] == 0x0d)
132 if (i >= (size - 1))
134 /* Last char, CR */
135 cr++;
137 else
139 if (buffer[i + 1] != 0x0a)
141 /* CR */
142 cr++;
144 else
146 /* CRLF */
147 crlf++;
149 i++;
154 /* Vote for the maximum */
155 mode = SC_EOL_LF;
156 max_mode = lf;
157 if (crlf > max_mode)
159 mode = SC_EOL_CRLF;
160 max_mode = crlf;
162 if (cr > max_mode)
164 mode = SC_EOL_CR;
165 max_mode = cr;
168 return mode;
172 gboolean utils_isbrace(gchar c, gboolean include_angles)
174 switch (c)
176 case '<':
177 case '>':
178 return include_angles;
180 case '(':
181 case ')':
182 case '{':
183 case '}':
184 case '[':
185 case ']': return TRUE;
186 default: return FALSE;
191 gboolean utils_is_opening_brace(gchar c, gboolean include_angles)
193 switch (c)
195 case '<':
196 return include_angles;
198 case '(':
199 case '{':
200 case '[': return TRUE;
201 default: return FALSE;
207 * Writes @a text into a file named @a filename.
208 * If the file doesn't exist, it will be created.
209 * If it already exists, it will be overwritten.
211 * @warning You should use @c g_file_set_contents() instead if you don't need
212 * file permissions and other metadata to be preserved, as that always handles
213 * disk exhaustion safely.
215 * @param filename The filename of the file to write, in locale encoding.
216 * @param text The text to write into the file.
218 * @return 0 if the file was successfully written, otherwise the @c errno of the
219 * failed operation is returned.
221 GEANY_API_SYMBOL
222 gint utils_write_file(const gchar *filename, const gchar *text)
224 g_return_val_if_fail(filename != NULL, ENOENT);
225 g_return_val_if_fail(text != NULL, EINVAL);
227 if (file_prefs.use_safe_file_saving)
229 GError *error = NULL;
230 if (! g_file_set_contents(filename, text, -1, &error))
232 geany_debug("%s: could not write to file %s (%s)", G_STRFUNC, filename, error->message);
233 g_error_free(error);
234 return EIO;
237 else
239 FILE *fp;
240 gsize bytes_written, len;
241 gboolean fail = FALSE;
243 if (filename == NULL)
244 return ENOENT;
246 len = strlen(text);
247 errno = 0;
248 fp = g_fopen(filename, "w");
249 if (fp == NULL)
250 fail = TRUE;
251 else
253 bytes_written = fwrite(text, sizeof(gchar), len, fp);
255 if (len != bytes_written)
257 fail = TRUE;
258 geany_debug(
259 "utils_write_file(): written only %"G_GSIZE_FORMAT" bytes, had to write %"G_GSIZE_FORMAT" bytes to %s",
260 bytes_written, len, filename);
262 if (fclose(fp) != 0)
263 fail = TRUE;
265 if (fail)
267 geany_debug("utils_write_file(): could not write to file %s (%s)",
268 filename, g_strerror(errno));
269 return FALLBACK(errno, EIO);
272 return 0;
276 /** Searches backward through @a size bytes looking for a '<'.
277 * @param sel .
278 * @param size .
279 * @return @nullable The tag name (newly allocated) or @c NULL if no opening tag was found.
281 GEANY_API_SYMBOL
282 gchar *utils_find_open_xml_tag(const gchar sel[], gint size)
284 const gchar *cur, *begin;
285 gsize len;
287 cur = utils_find_open_xml_tag_pos(sel, size);
288 if (cur == NULL)
289 return NULL;
291 cur++; /* skip the bracket */
292 begin = cur;
293 while (strchr(":_-.", *cur) || isalnum(*cur))
294 cur++;
296 len = (gsize)(cur - begin);
297 return len ? g_strndup(begin, len) : NULL;
301 /** Searches backward through @a size bytes looking for a '<'.
302 * @param sel .
303 * @param size .
304 * @return @nullable pointer to '<' of the found opening tag within @a sel, or @c NULL if no opening tag was found.
306 GEANY_API_SYMBOL
307 const gchar *utils_find_open_xml_tag_pos(const gchar sel[], gint size)
309 /* stolen from anjuta and modified */
310 const gchar *begin, *cur;
312 if (G_UNLIKELY(size < 3))
313 { /* Smallest tag is "<p>" which is 3 characters */
314 return NULL;
316 begin = &sel[0];
317 cur = &sel[size - 1];
319 /* Skip to the character before the closing brace */
320 while (cur > begin)
322 if (*cur == '>')
323 break;
324 --cur;
326 --cur;
327 /* skip whitespace */
328 while (cur > begin && isspace(*cur))
329 cur--;
330 if (*cur == '/')
331 return NULL; /* we found a short tag which doesn't need to be closed */
332 while (cur > begin)
334 if (*cur == '<')
335 break;
336 /* exit immediately if such non-valid XML/HTML is detected, e.g. "<script>if a >" */
337 else if (*cur == '>')
338 break;
339 --cur;
342 /* if the found tag is an opening, not a closing tag or empty <> */
343 if (*cur == '<' && *(cur + 1) != '/' && *(cur + 1) != '>')
344 return cur;
346 return NULL;
350 /* Returns true if tag_name is a self-closing tag */
351 gboolean utils_is_short_html_tag(const gchar *tag_name)
353 const gchar names[][20] = {
354 "area",
355 "base",
356 "basefont", /* < or not < */
357 "br",
358 "col",
359 "command",
360 "embed",
361 "frame",
362 "hr",
363 "img",
364 "input",
365 "keygen",
366 "link",
367 "meta",
368 "param",
369 "source",
370 "track",
371 "wbr"
374 if (tag_name)
376 if (bsearch(tag_name, names, G_N_ELEMENTS(names), 20,
377 (GCompareFunc)g_ascii_strcasecmp))
378 return TRUE;
380 return FALSE;
384 const gchar *utils_get_eol_name(gint eol_mode)
386 switch (eol_mode)
388 case SC_EOL_CRLF: return _("Windows (CRLF)"); break;
389 case SC_EOL_CR: return _("Classic Mac (CR)"); break;
390 default: return _("Unix (LF)"); break;
395 const gchar *utils_get_eol_short_name(gint eol_mode)
397 switch (eol_mode)
399 case SC_EOL_CRLF: return _("CRLF"); break;
400 case SC_EOL_CR: return _("CR"); break;
401 default: return _("LF"); break;
406 const gchar *utils_get_eol_char(gint eol_mode)
408 switch (eol_mode)
410 case SC_EOL_CRLF: return "\r\n"; break;
411 case SC_EOL_CR: return "\r"; break;
412 default: return "\n"; break;
417 /* Converts line endings to @a target_eol_mode. */
418 void utils_ensure_same_eol_characters(GString *string, gint target_eol_mode)
420 const gchar *eol_str = utils_get_eol_char(target_eol_mode);
422 /* first convert data to LF only */
423 utils_string_replace_all(string, "\r\n", "\n");
424 utils_string_replace_all(string, "\r", "\n");
426 if (target_eol_mode == SC_EOL_LF)
427 return;
429 /* now convert to desired line endings */
430 utils_string_replace_all(string, "\n", eol_str);
434 gboolean utils_atob(const gchar *str)
436 if (G_UNLIKELY(str == NULL))
437 return FALSE;
438 else if (strcmp(str, "TRUE") == 0 || strcmp(str, "true") == 0)
439 return TRUE;
440 return FALSE;
444 /* NULL-safe version of g_path_is_absolute(). */
445 gboolean utils_is_absolute_path(const gchar *path)
447 if (G_UNLIKELY(EMPTY(path)))
448 return FALSE;
450 return g_path_is_absolute(path);
454 /* Skips root if path is absolute, do nothing otherwise.
455 * This is a relative-safe version of g_path_skip_root().
457 const gchar *utils_path_skip_root(const gchar *path)
459 const gchar *path_relative;
461 path_relative = g_path_skip_root(path);
463 return (path_relative != NULL) ? path_relative : path;
467 /* Convert a fractional @a val in the range [0, 1] to a whole value in the range [0, @a factor].
468 * In particular, this is used for converting a @c GdkColor to the "#RRGGBB" format in a way that
469 * agrees with GTK+, so the "#RRGGBB" in the color picker is the same "#RRGGBB" that is inserted
470 * into the document. See https://github.com/geany/geany/issues/1527
472 gdouble utils_scale_round(gdouble val, gdouble factor)
474 val = floor(val * factor + 0.5);
475 val = MAX(val, 0);
476 val = MIN(val, factor);
478 return val;
482 /* like g_utf8_strdown() but if @str is not valid UTF8, convert it from locale first.
483 * returns NULL on charset conversion failure */
484 gchar *utils_utf8_strdown(const gchar *str)
486 gchar *down;
488 if (g_utf8_validate(str, -1, NULL))
489 down = g_utf8_strdown(str, -1);
490 else
492 down = g_locale_to_utf8(str, -1, NULL, NULL, NULL);
493 if (down)
494 SETPTR(down, g_utf8_strdown(down, -1));
497 return down;
502 * A replacement function for g_strncasecmp() to compare strings case-insensitive.
503 * It converts both strings into lowercase using g_utf8_strdown() and then compare
504 * both strings using strcmp().
505 * This is not completely accurate regarding locale-specific case sorting rules
506 * but seems to be a good compromise between correctness and performance.
508 * The input strings should be in UTF-8 or locale encoding.
510 * @param s1 @nullable Pointer to first string or @c NULL.
511 * @param s2 @nullable Pointer to second string or @c NULL.
513 * @return an integer less than, equal to, or greater than zero if @a s1 is found, respectively,
514 * to be less than, to match, or to be greater than @a s2.
516 * @since 0.16
518 GEANY_API_SYMBOL
519 gint utils_str_casecmp(const gchar *s1, const gchar *s2)
521 gchar *tmp1, *tmp2;
522 gint result;
524 g_return_val_if_fail(s1 != NULL, 1);
525 g_return_val_if_fail(s2 != NULL, -1);
527 /* ensure strings are UTF-8 and lowercase */
528 tmp1 = utils_utf8_strdown(s1);
529 if (! tmp1)
530 return 1;
531 tmp2 = utils_utf8_strdown(s2);
532 if (! tmp2)
534 g_free(tmp1);
535 return -1;
538 /* compare */
539 result = strcmp(tmp1, tmp2);
541 g_free(tmp1);
542 g_free(tmp2);
543 return result;
548 * Truncates the input string to a given length.
549 * Characters are removed from the middle of the string, so the start and the end of string
550 * won't change.
552 * @param string Input string.
553 * @param truncate_length The length in characters of the resulting string.
555 * @return A copy of @a string which is truncated to @a truncate_length characters,
556 * should be freed when no longer needed.
558 * @since 0.17
560 /* This following function is taken from Gedit. */
561 GEANY_API_SYMBOL
562 gchar *utils_str_middle_truncate(const gchar *string, guint truncate_length)
564 GString *truncated;
565 guint length;
566 guint n_chars;
567 guint num_left_chars;
568 guint right_offset;
569 guint delimiter_length;
570 const gchar *delimiter = "\342\200\246";
572 g_return_val_if_fail(string != NULL, NULL);
574 length = strlen(string);
576 g_return_val_if_fail(g_utf8_validate(string, length, NULL), NULL);
578 /* It doesn't make sense to truncate strings to less than the size of the delimiter plus 2
579 * characters (one on each side) */
580 delimiter_length = g_utf8_strlen(delimiter, -1);
581 if (truncate_length < (delimiter_length + 2))
582 return g_strdup(string);
584 n_chars = g_utf8_strlen(string, length);
586 /* Make sure the string is not already small enough. */
587 if (n_chars <= truncate_length)
588 return g_strdup (string);
590 /* Find the 'middle' where the truncation will occur. */
591 num_left_chars = (truncate_length - delimiter_length) / 2;
592 right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
594 truncated = g_string_new_len(string, g_utf8_offset_to_pointer(string, num_left_chars) - string);
595 g_string_append(truncated, delimiter);
596 g_string_append(truncated, g_utf8_offset_to_pointer(string, right_offset));
598 return g_string_free(truncated, FALSE);
603 * @c NULL-safe string comparison. Returns @c TRUE if both @a a and @a b are @c NULL
604 * or if @a a and @a b refer to valid strings which are equal.
606 * @param a @nullable Pointer to first string or @c NULL.
607 * @param b @nullable Pointer to second string or @c NULL.
609 * @return @c TRUE if @a a equals @a b, else @c FALSE.
611 GEANY_API_SYMBOL
612 gboolean utils_str_equal(const gchar *a, const gchar *b)
614 /* (taken from libexo from os-cillation) */
615 if (a == NULL && b == NULL) return TRUE;
616 else if (a == NULL || b == NULL) return FALSE;
618 return strcmp(a, b) == 0;
623 * Removes the extension from @a filename and return the result in a newly allocated string.
625 * @param filename The filename to operate on.
627 * @return A newly-allocated string, should be freed when no longer needed.
629 GEANY_API_SYMBOL
630 gchar *utils_remove_ext_from_filename(const gchar *filename)
632 gchar *last_dot;
633 gchar *result;
634 gsize len;
636 g_return_val_if_fail(filename != NULL, NULL);
638 last_dot = strrchr(filename, '.');
639 if (! last_dot)
640 return g_strdup(filename);
642 len = (gsize) (last_dot - filename);
643 result = g_malloc(len + 1);
644 memcpy(result, filename, len);
645 result[len] = 0;
647 return result;
651 gchar utils_brace_opposite(gchar ch)
653 switch (ch)
655 case '(': return ')';
656 case ')': return '(';
657 case '[': return ']';
658 case ']': return '[';
659 case '{': return '}';
660 case '}': return '{';
661 case '<': return '>';
662 case '>': return '<';
663 default: return '\0';
668 /* Checks whether the given file can be written. locale_filename is expected in locale encoding.
669 * Returns 0 if it can be written, otherwise it returns errno */
670 gint utils_is_file_writable(const gchar *locale_filename)
672 gchar *file;
673 gint ret;
675 if (! g_file_test(locale_filename, G_FILE_TEST_EXISTS) &&
676 ! g_file_test(locale_filename, G_FILE_TEST_IS_DIR))
677 /* get the file's directory to check for write permission if it doesn't yet exist */
678 file = g_path_get_dirname(locale_filename);
679 else
680 file = g_strdup(locale_filename);
682 #ifdef G_OS_WIN32
683 /* use _waccess on Windows, access() doesn't accept special characters */
684 ret = win32_check_write_permission(file);
685 #else
687 /* access set also errno to "FILE NOT FOUND" even if locale_filename is writeable, so use
688 * errno only when access() explicitly returns an error */
689 if (access(file, R_OK | W_OK) != 0)
690 ret = errno;
691 else
692 ret = 0;
693 #endif
694 g_free(file);
695 return ret;
699 /* Replaces all occurrences of needle in haystack with replacement.
700 * Warning: *haystack must be a heap address; it may be freed and reassigned.
701 * Note: utils_string_replace_all() will always be faster when @a replacement is longer
702 * than @a needle.
703 * All strings have to be NULL-terminated.
704 * See utils_string_replace_all() for details. */
705 void utils_str_replace_all(gchar **haystack, const gchar *needle, const gchar *replacement)
707 GString *str;
709 g_return_if_fail(*haystack != NULL);
711 str = g_string_new(*haystack);
713 g_free(*haystack);
714 utils_string_replace_all(str, needle, replacement);
716 *haystack = g_string_free(str, FALSE);
720 gint utils_strpos(const gchar *haystack, const gchar *needle)
722 const gchar *sub;
724 if (! *needle)
725 return -1;
727 sub = strstr(haystack, needle);
728 if (! sub)
729 return -1;
731 return sub - haystack;
736 * Retrieves a formatted date/time string from GDateTime.
737 * This function works on UTF-8 encoded strings.
739 * @param format The format string to pass to g_date_time_format, in UTF-8 encoding.
740 See https://docs.gtk.org/glib/method.DateTime.format.html for details.
741 * @param time_to_use @nullable The date/time to use, in time_t format or @c NULL to use the current time.
743 * @return A newly-allocated string, should be freed when no longer needed.
745 * @since 0.16
747 GEANY_API_SYMBOL
748 gchar *utils_get_date_time(const gchar *format, time_t *time_to_use)
750 time_t unixtime;
751 gchar *datetime_formatted;
752 GDateTime *datetime;
754 g_return_val_if_fail(format != NULL, NULL);
756 if (time_to_use != NULL)
757 unixtime = *time_to_use;
758 else
759 unixtime = time(NULL);
761 datetime = g_date_time_new_from_unix_local(unixtime);
762 datetime_formatted = g_date_time_format(datetime, format);
764 g_date_time_unref(datetime);
765 return datetime_formatted;
769 gchar *utils_get_initials(const gchar *name)
771 gint i = 1, j = 1;
772 gchar *initials = g_malloc0(5);
774 initials[0] = name[0];
775 while (name[i] != '\0' && j < 4)
777 if (name[i] == ' ' && name[i + 1] != ' ')
779 initials[j++] = name[i + 1];
781 i++;
783 return initials;
788 * Wraps g_key_file_get_integer() to add a default value argument.
790 * @param config A GKeyFile object.
791 * @param section The group name to look in for the key.
792 * @param key The key to find.
793 * @param default_value The default value which will be returned when @a section or @a key
794 * don't exist.
796 * @return The value associated with @a key as an integer, or the given default value if the value
797 * could not be retrieved.
799 GEANY_API_SYMBOL
800 gint utils_get_setting_integer(GKeyFile *config, const gchar *section, const gchar *key,
801 const gint default_value)
803 gint tmp;
804 GError *error = NULL;
806 g_return_val_if_fail(config, default_value);
808 tmp = g_key_file_get_integer(config, section, key, &error);
809 if (error)
811 g_error_free(error);
812 return default_value;
814 return tmp;
819 * Wraps g_key_file_get_boolean() to add a default value argument.
821 * @param config A GKeyFile object.
822 * @param section The group name to look in for the key.
823 * @param key The key to find.
824 * @param default_value The default value which will be returned when @c section or @c key
825 * don't exist.
827 * @return The value associated with @a key as a boolean, or the given default value if the value
828 * could not be retrieved.
830 GEANY_API_SYMBOL
831 gboolean utils_get_setting_boolean(GKeyFile *config, const gchar *section, const gchar *key,
832 const gboolean default_value)
834 gboolean tmp;
835 GError *error = NULL;
837 g_return_val_if_fail(config, default_value);
839 tmp = g_key_file_get_boolean(config, section, key, &error);
840 if (error)
842 g_error_free(error);
843 return default_value;
845 return tmp;
850 * Wraps g_key_file_get_double() to add a default value argument.
852 * @param config A GKeyFile object.
853 * @param section The group name to look in for the key.
854 * @param key The key to find.
855 * @param default_value The default value which will be returned when @a section or @a key
856 * don't exist.
858 * @return The value associated with @a key as an integer, or the given default value if the value
859 * could not be retrieved.
861 GEANY_API_SYMBOL
862 gdouble utils_get_setting_double(GKeyFile *config, const gchar *section, const gchar *key,
863 const gdouble default_value)
865 gdouble tmp;
866 GError *error = NULL;
868 g_return_val_if_fail(config, default_value);
870 tmp = g_key_file_get_double(config, section, key, &error);
871 if (error)
873 g_error_free(error);
874 return default_value;
876 return tmp;
881 * Wraps g_key_file_get_string() to add a default value argument.
883 * @param config A GKeyFile object.
884 * @param section The group name to look in for the key.
885 * @param key The key to find.
886 * @param default_value The default value which will be returned when @a section or @a key
887 * don't exist.
889 * @return A newly allocated string, either the value for @a key or a copy of the given
890 * default value if it could not be retrieved.
892 GEANY_API_SYMBOL
893 gchar *utils_get_setting_string(GKeyFile *config, const gchar *section, const gchar *key,
894 const gchar *default_value)
896 gchar *tmp;
898 g_return_val_if_fail(config, g_strdup(default_value));
900 tmp = g_key_file_get_string(config, section, key, NULL);
901 if (!tmp)
903 return g_strdup(default_value);
905 return tmp;
909 gchar *utils_get_hex_from_color(GdkColor *color)
911 g_return_val_if_fail(color != NULL, NULL);
913 return g_strdup_printf("#%02X%02X%02X",
914 (guint) (utils_scale_round(color->red / 65535.0, 255)),
915 (guint) (utils_scale_round(color->green / 65535.0, 255)),
916 (guint) (utils_scale_round(color->blue / 65535.0, 255)));
920 /* Get directory from current file in the notebook.
921 * Returns dir string that should be freed or NULL, depending on whether current file is valid.
922 * Returned string is in UTF-8 encoding */
923 gchar *utils_get_current_file_dir_utf8(void)
925 GeanyDocument *doc = document_get_current();
927 if (doc != NULL)
929 /* get current filename */
930 const gchar *cur_fname = doc->file_name;
932 if (cur_fname != NULL)
934 /* get folder part from current filename */
935 return g_path_get_dirname(cur_fname); /* returns "." if no path */
939 return NULL; /* no file open */
943 /* very simple convenience function */
944 void utils_beep(void)
946 if (prefs.beep_on_errors)
947 gdk_beep();
951 /* converts a color representation using gdk_color_parse(), with additional
952 * support of the "0x" prefix as a synonym for "#" */
953 gboolean utils_parse_color(const gchar *spec, GdkColor *color)
955 gchar buf[64] = {0};
957 g_return_val_if_fail(spec != NULL, -1);
959 if (spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X'))
961 /* convert to # format for GDK to understand it */
962 buf[0] = '#';
963 strncpy(buf + 1, spec + 2, sizeof(buf) - 2);
964 spec = buf;
967 return gdk_color_parse(spec, color);
971 /* converts a GdkColor to the packed 24 bits BGR format, as understood by Scintilla
972 * returns a 24 bits BGR color, or -1 on failure */
973 gint utils_color_to_bgr(const GdkColor *c)
975 g_return_val_if_fail(c != NULL, -1);
976 return (c->red / 256) | ((c->green / 256) << 8) | ((c->blue / 256) << 16);
980 /* parses @p spec using utils_parse_color() and convert it to 24 bits BGR using
981 * utils_color_to_bgr() */
982 gint utils_parse_color_to_bgr(const gchar *spec)
984 GdkColor color;
985 if (utils_parse_color(spec, &color))
986 return utils_color_to_bgr(&color);
987 else
988 return -1;
992 /* Returns: newly allocated string with the current time formatted HH:MM:SS.
993 * If "include_microseconds" is TRUE, microseconds are appended.
995 * The returned string should be freed with g_free(). */
996 gchar *utils_get_current_time_string(gboolean include_microseconds)
998 // "%f" specifier for microseconds is only available since GLib 2.66
999 if (glib_check_version(2, 66, 0) != NULL)
1000 include_microseconds = FALSE;
1002 GDateTime *now = g_date_time_new_now_local();
1003 const gchar *format = include_microseconds ? "%H:%M:%S.%f" : "%H:%M:%S";
1004 gchar *time_string = g_date_time_format(now, format);
1005 g_date_time_unref(now);
1006 return time_string;
1010 GIOChannel *utils_set_up_io_channel(
1011 gint fd, GIOCondition cond, gboolean nblock, GIOFunc func, gpointer data)
1013 GIOChannel *ioc;
1014 /*const gchar *encoding;*/
1016 #ifdef G_OS_WIN32
1017 ioc = g_io_channel_win32_new_fd(fd);
1018 #else
1019 ioc = g_io_channel_unix_new(fd);
1020 #endif
1022 if (nblock)
1023 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
1025 g_io_channel_set_encoding(ioc, NULL, NULL);
1027 if (! g_get_charset(&encoding))
1028 { // hope this works reliably
1029 GError *error = NULL;
1030 g_io_channel_set_encoding(ioc, encoding, &error);
1031 if (error)
1033 geany_debug("%s: %s", G_STRFUNC, error->message);
1034 g_error_free(error);
1035 return ioc;
1039 /* "auto-close" ;-) */
1040 g_io_channel_set_close_on_unref(ioc, TRUE);
1042 g_io_add_watch(ioc, cond, func, data);
1043 g_io_channel_unref(ioc);
1045 return ioc;
1049 /* Contributed by Stefan Oltmanns, thanks.
1050 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1051 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1052 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1054 gsize i, j, len;
1055 guint unicodechar;
1057 g_return_val_if_fail(string != NULL, FALSE);
1059 j = 0;
1060 len = strlen(string);
1061 for (i = 0; i < len; i++)
1063 if (string[i]=='\\')
1065 if (i++ >= strlen(string))
1067 return FALSE;
1069 switch (string[i])
1071 case '\\':
1072 if (keep_backslash)
1073 string[j++] = '\\';
1074 string[j] = '\\';
1075 break;
1076 case 'n':
1077 string[j] = '\n';
1078 break;
1079 case 'r':
1080 string[j] = '\r';
1081 break;
1082 case 't':
1083 string[j] = '\t';
1084 break;
1085 #if 0
1086 case 'x': /* Warning: May produce illegal utf-8 string! */
1087 i += 2;
1088 if (i >= strlen(string))
1090 return FALSE;
1092 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1093 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1094 else return FALSE;
1095 string[j] <<= 4;
1096 if (isdigit(string[i])) string[j] |= string[i] - 48;
1097 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1098 else return FALSE;
1099 break;
1100 #endif
1101 case 'u':
1103 i += 2;
1104 if (i >= strlen(string))
1106 return FALSE;
1108 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1109 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1110 else return FALSE;
1111 unicodechar <<= 4;
1112 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1113 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1114 else return FALSE;
1115 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1116 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1118 i += 2;
1119 unicodechar <<= 8;
1120 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1121 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1122 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1123 else unicodechar |= tolower(string[i])-87;
1125 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1126 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1128 i += 2;
1129 unicodechar <<= 8;
1130 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1131 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1132 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1133 else unicodechar |= tolower(string[i])-87;
1135 if (unicodechar < 0x80)
1137 string[j] = unicodechar;
1139 else if (unicodechar < 0x800)
1141 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1142 j++;
1143 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1145 else if (unicodechar < 0x10000)
1147 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1148 j++;
1149 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1150 j++;
1151 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1153 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1155 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1156 j++;
1157 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1158 j++;
1159 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1160 j++;
1161 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1163 else
1165 return FALSE;
1167 break;
1169 default:
1170 /* unnecessary escapes are allowed */
1171 if (keep_backslash)
1172 string[j++] = '\\';
1173 string[j] = string[i];
1176 else
1178 string[j] = string[i];
1180 j++;
1182 while (j < i)
1184 string[j] = 0;
1185 j++;
1187 return TRUE;
1191 /* Wraps a string in place, replacing a space with a newline character.
1192 * wrapstart is the minimum position to start wrapping or -1 for default */
1193 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1195 gchar *pos, *linestart;
1196 gboolean ret = FALSE;
1198 if (wrapstart < 0)
1199 wrapstart = 80;
1201 for (pos = linestart = string; *pos != '\0'; pos++)
1203 if (pos - linestart >= wrapstart && *pos == ' ')
1205 *pos = '\n';
1206 linestart = pos;
1207 ret = TRUE;
1210 return ret;
1215 * Converts the given UTF-8 encoded string into locale encoding.
1216 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1218 * @param utf8_text UTF-8 encoded text.
1220 * @return The converted string in locale encoding, or a copy of the input string if conversion
1221 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1223 GEANY_API_SYMBOL
1224 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1226 #ifdef G_OS_WIN32
1227 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1228 * which would result in wrongly converted strings */
1229 return g_strdup(utf8_text);
1230 #else
1231 gchar *locale_text;
1233 if (! utf8_text)
1234 return NULL;
1235 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1236 if (locale_text == NULL)
1237 locale_text = g_strdup(utf8_text);
1238 return locale_text;
1239 #endif
1244 * Converts the given string (in locale encoding) into UTF-8 encoding.
1245 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1247 * @param locale_text Text in locale encoding.
1249 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1250 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1252 GEANY_API_SYMBOL
1253 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1255 #ifdef G_OS_WIN32
1256 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1257 * which would result in wrongly converted strings */
1258 return g_strdup(locale_text);
1259 #else
1260 gchar *utf8_text;
1262 if (! locale_text)
1263 return NULL;
1264 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1265 if (utf8_text == NULL)
1266 utf8_text = g_strdup(locale_text);
1267 return utf8_text;
1268 #endif
1272 /* Pass pointers to free after arg_count.
1273 * The last argument must be NULL as an extra check that arg_count is correct. */
1274 void utils_free_pointers(gsize arg_count, ...)
1276 va_list a;
1277 gsize i;
1278 gpointer ptr;
1280 va_start(a, arg_count);
1281 for (i = 0; i < arg_count; i++)
1283 ptr = va_arg(a, gpointer);
1284 g_free(ptr);
1286 ptr = va_arg(a, gpointer);
1287 if (ptr)
1288 g_warning("Wrong arg_count!");
1289 va_end(a);
1293 /* Creates a string array deep copy of a series of non-NULL strings.
1294 * The first argument is nothing special and must not be NULL.
1295 * The list must be terminated with NULL. */
1296 GEANY_EXPORT_SYMBOL
1297 gchar **utils_strv_new(const gchar *first, ...)
1299 gsize strvlen, i;
1300 va_list args;
1301 gchar *str;
1302 gchar **strv;
1304 g_return_val_if_fail(first != NULL, NULL);
1306 strvlen = 1; /* for first argument */
1308 /* count other arguments */
1309 va_start(args, first);
1310 for (; va_arg(args, gchar*) != NULL; strvlen++);
1311 va_end(args);
1313 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1314 strv[0] = g_strdup(first);
1316 va_start(args, first);
1317 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1319 strv[i] = g_strdup(str);
1321 va_end(args);
1323 strv[i] = NULL;
1324 return strv;
1329 * Creates a directory if it doesn't already exist.
1330 * Creates intermediate parent directories as needed, too.
1331 * The permissions of the created directory are set 0700.
1333 * @param path The path of the directory to create, in locale encoding.
1334 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1336 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1337 * failed operation is returned.
1339 GEANY_API_SYMBOL
1340 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1342 gint mode = 0700;
1343 gint result;
1345 if (path == NULL || strlen(path) == 0)
1346 return EFAULT;
1348 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1349 if (result != 0)
1350 return errno;
1351 return 0;
1356 * Gets a list of files from the specified directory.
1357 * Locale encoding is expected for @a path and used for the file list. The list and the data
1358 * in the list should be freed after use, e.g.:
1359 * @code
1360 * g_slist_foreach(list, (GFunc) g_free, NULL);
1361 * g_slist_free(list); @endcode
1363 * @note If you don't need a list you should use the foreach_dir() macro instead -
1364 * it's more efficient.
1366 * @param path The path of the directory to scan, in locale encoding.
1367 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1368 * will use more memory.
1369 * @param sort Whether to sort alphabetically (UTF-8 safe).
1370 * @param error The location for storing a possible error, or @c NULL.
1372 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL if
1373 * no files were found. The list and its data should be freed when no longer needed.
1374 * @see utils_get_file_list().
1376 GEANY_API_SYMBOL
1377 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1379 GSList *list = NULL;
1380 GDir *dir;
1381 const gchar *filename;
1383 if (error)
1384 *error = NULL;
1385 g_return_val_if_fail(path != NULL, NULL);
1387 dir = g_dir_open(path, 0, error);
1388 if (dir == NULL)
1389 return NULL;
1391 foreach_dir(filename, dir)
1393 list = g_slist_prepend(list, full_path ?
1394 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1396 g_dir_close(dir);
1397 /* sorting last is quicker than on insertion */
1398 if (sort)
1399 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1400 return list;
1405 * Gets a sorted list of files from the specified directory.
1406 * Locale encoding is expected for @a path and used for the file list. The list and the data
1407 * in the list should be freed after use, e.g.:
1408 * @code
1409 * g_slist_foreach(list, (GFunc) g_free, NULL);
1410 * g_slist_free(list); @endcode
1412 * @param path The path of the directory to scan, in locale encoding.
1413 * @param length The location to store the number of non-@c NULL data items in the list,
1414 * unless @c NULL.
1415 * @param error The location for storing a possible error, or @c NULL.
1417 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL
1418 * if no files were found. The list and its data should be freed when no longer needed.
1419 * @see utils_get_file_list_full().
1421 GEANY_API_SYMBOL
1422 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1424 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1426 if (length)
1427 *length = g_slist_length(list);
1428 return list;
1432 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1433 gboolean utils_str_has_upper(const gchar *str)
1435 gunichar c;
1437 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1438 return FALSE;
1440 while (*str != '\0')
1442 c = g_utf8_get_char(str);
1443 /* check only letters and stop once the first non-capital was found */
1444 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1445 return TRUE;
1446 /* FIXME don't write a const string */
1447 str = g_utf8_next_char(str);
1449 return FALSE;
1453 /* end can be -1 for haystack->len.
1454 * returns: position of found text or -1. */
1455 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1457 gint pos;
1459 g_return_val_if_fail(haystack != NULL, -1);
1460 if (haystack->len == 0)
1461 return -1;
1463 g_return_val_if_fail(start >= 0, -1);
1464 if (start >= (gint)haystack->len)
1465 return -1;
1467 g_return_val_if_fail(!EMPTY(needle), -1);
1469 if (end < 0)
1470 end = haystack->len;
1472 pos = utils_strpos(haystack->str + start, needle);
1473 if (pos == -1)
1474 return -1;
1476 pos += start;
1477 if (pos >= end)
1478 return -1;
1479 return pos;
1483 /* Replaces @len characters from offset @a pos.
1484 * len can be -1 to replace the remainder of @a str.
1485 * returns: pos + strlen(replace). */
1486 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1488 g_string_erase(str, pos, len);
1489 if (replace)
1491 g_string_insert(str, pos, replace);
1492 pos += strlen(replace);
1494 return pos;
1499 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1500 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1501 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1503 * @param haystack The input string to operate on. This string is modified in place.
1504 * @param needle The string which should be replaced.
1505 * @param replace The replacement for @a needle.
1507 * @return Number of replacements made.
1509 GEANY_API_SYMBOL
1510 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1512 guint count = 0;
1513 gint pos = 0;
1514 gsize needle_length = strlen(needle);
1516 while (1)
1518 pos = utils_string_find(haystack, pos, -1, needle);
1520 if (pos == -1)
1521 break;
1523 pos = utils_string_replace(haystack, pos, needle_length, replace);
1524 count++;
1526 return count;
1531 * Replaces only the first occurrence of @a needle in @a haystack
1532 * with @a replace.
1533 * For details, see utils_string_replace_all().
1535 * @param haystack The input string to operate on. This string is modified in place.
1536 * @param needle The string which should be replaced.
1537 * @param replace The replacement for @a needle.
1539 * @return Number of replacements made.
1541 * @since 0.16
1543 GEANY_API_SYMBOL
1544 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1546 gint pos = utils_string_find(haystack, 0, -1, needle);
1548 if (pos == -1)
1549 return 0;
1551 utils_string_replace(haystack, pos, strlen(needle), replace);
1552 return 1;
1556 /* Similar to g_regex_replace but allows matching a subgroup.
1557 * match_num: which match to replace, 0 for whole match.
1558 * literal: FALSE to interpret escape sequences in @a replace.
1559 * returns: number of replacements.
1560 * bug: replaced text can affect matching of ^ or \b */
1561 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1562 guint match_num, const gchar *replace, gboolean literal)
1564 GMatchInfo *minfo;
1565 guint ret = 0;
1566 gint start = 0;
1568 g_assert(literal); /* escapes not implemented yet */
1569 g_return_val_if_fail(replace, 0);
1571 /* ensure haystack->str is not null */
1572 if (haystack->len == 0)
1573 return 0;
1575 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1576 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1578 gint end, len;
1580 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1581 len = end - start;
1582 utils_string_replace(haystack, start, len, replace);
1583 ret++;
1585 /* skip past whole match */
1586 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1587 start = end - len + strlen(replace);
1588 g_match_info_free(minfo);
1590 g_match_info_free(minfo);
1591 return ret;
1595 /* Get project or default startup directory (if set), or NULL. */
1596 const gchar *utils_get_default_dir_utf8(void)
1598 if (app->project && !EMPTY(app->project->base_path))
1600 return app->project->base_path;
1603 if (!EMPTY(prefs.default_open_path))
1605 return prefs.default_open_path;
1607 return NULL;
1612 * Wraps @c spawn_sync(), which see.
1614 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1615 * @param argv The child's argument vector.
1616 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1617 * @param flags Ignored.
1618 * @param child_setup @girskip Ignored.
1619 * @param user_data @girskip Ignored.
1620 * @param std_out @out @optional The return location for child output, or @c NULL.
1621 * @param std_err @out @optional The return location for child error messages, or @c NULL.
1622 * @param exit_status @out @optional The child exit status, as returned by waitpid(), or @c NULL.
1623 * @param error The return location for error or @c NULL.
1625 * @return @c TRUE on success, @c FALSE if an error was set.
1627 GEANY_API_SYMBOL
1628 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1629 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1630 gchar **std_err, gint *exit_status, GError **error)
1632 GString *output = std_out ? g_string_new(NULL) : NULL;
1633 GString *errors = std_err ? g_string_new(NULL) : NULL;
1634 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1636 if (std_out)
1637 *std_out = g_string_free(output, !result);
1639 if (std_err)
1640 *std_err = g_string_free(errors, !result);
1642 return result;
1647 * Wraps @c spawn_async(), which see.
1649 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1650 * @param argv The child's argument vector.
1651 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1652 * @param flags Ignored.
1653 * @param child_setup @girskip Ignored.
1654 * @param user_data Ignored.
1655 * @param child_pid @out @nullable The return location for child process ID, or @c NULL.
1656 * @param error The return location for error or @c NULL.
1658 * @return @c TRUE on success, @c FALSE if an error was set.
1660 GEANY_API_SYMBOL
1661 gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1662 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1663 GError **error)
1665 return spawn_async(dir, NULL, argv, env, child_pid, error);
1669 /* Returns "file:///" on Windows, "file://" everywhere else */
1670 const gchar *utils_get_uri_file_prefix(void)
1672 #ifdef G_OS_WIN32
1673 return "file:///";
1674 #else
1675 return "file://";
1676 #endif
1680 /* Retrieves the path for the given URI.
1681 * It returns:
1682 * - the path which was determined by g_filename_from_uri() or GIO
1683 * - NULL if the URI is non-local and gvfs-fuse is not installed
1684 * - a new copy of 'uri' if it is not an URI. */
1685 gchar *utils_get_path_from_uri(const gchar *uri)
1687 gchar *locale_filename;
1689 g_return_val_if_fail(uri != NULL, NULL);
1691 if (! utils_is_uri(uri))
1692 return g_strdup(uri);
1694 /* this will work only for 'file://' URIs */
1695 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1696 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1697 if (locale_filename == NULL)
1699 GFile *file = g_file_new_for_uri(uri);
1700 locale_filename = g_file_get_path(file);
1701 g_object_unref(file);
1702 if (locale_filename == NULL)
1704 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1705 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1709 return locale_filename;
1713 gboolean utils_is_uri(const gchar *uri)
1715 g_return_val_if_fail(uri != NULL, FALSE);
1717 return (strstr(uri, "://") != NULL);
1721 /* path should be in locale encoding */
1722 gboolean utils_is_remote_path(const gchar *path)
1724 g_return_val_if_fail(path != NULL, FALSE);
1726 /* if path is an URI and it doesn't start "file://", we take it as remote */
1727 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1728 return TRUE;
1730 #ifndef G_OS_WIN32
1732 static gchar *fuse_path = NULL;
1733 static gsize len = 0;
1735 if (G_UNLIKELY(fuse_path == NULL))
1737 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1738 len = strlen(fuse_path);
1740 /* Comparing the file path against a hardcoded path is not the most elegant solution
1741 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1742 * proper GFile objects for Fuse paths, but it only does in future GVFS
1743 * versions (gvfs 1.1.1). */
1744 return (strncmp(path, fuse_path, len) == 0);
1746 #endif
1748 return FALSE;
1752 /* Remove all relative and untidy elements from the path of @a filename.
1753 * @param filename must be a valid absolute path.
1754 * @see utils_get_real_path() - also resolves links. */
1755 void utils_tidy_path(gchar *filename)
1757 GString *str;
1758 const gchar *needle;
1759 gboolean preserve_double_backslash = FALSE;
1761 g_return_if_fail(g_path_is_absolute(filename));
1763 str = g_string_new(filename);
1765 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1766 preserve_double_backslash = TRUE;
1768 #ifdef G_OS_WIN32
1769 /* using MSYS we can get Unix-style separators */
1770 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1771 #endif
1772 /* replace "/./" and "//" */
1773 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1774 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1776 if (preserve_double_backslash)
1777 g_string_prepend(str, "\\");
1779 /* replace "/../" */
1780 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1781 while (1)
1783 const gchar *c = strstr(str->str, needle);
1784 if (c == NULL)
1785 break;
1786 else
1788 gssize pos, sub_len;
1790 pos = c - str->str;
1791 if (pos <= 3)
1792 break; /* bad path */
1794 /* replace "/../" */
1795 g_string_erase(str, pos, strlen(needle));
1796 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1798 /* search for last "/" before found "/../" */
1799 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1800 sub_len = pos - (c - str->str);
1801 if (! c)
1802 break; /* bad path */
1804 pos = c - str->str; /* position of previous "/" */
1805 g_string_erase(str, pos, sub_len);
1808 if (str->len <= strlen(filename))
1809 memcpy(filename, str->str, str->len + 1);
1810 else
1811 g_warn_if_reached();
1812 g_string_free(str, TRUE);
1817 * Removes characters from a string, in place.
1819 * @param string String to search.
1820 * @param chars Characters to remove.
1822 * @return @a string - return value is only useful when nesting function calls, e.g.:
1823 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1825 * @see @c g_strdelimit.
1827 GEANY_API_SYMBOL
1828 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1830 const gchar *r;
1831 gchar *w = string;
1833 g_return_val_if_fail(string, NULL);
1834 if (G_UNLIKELY(EMPTY(chars)))
1835 return string;
1837 foreach_str(r, string)
1839 if (!strchr(chars, *r))
1840 *w++ = *r;
1842 *w = 0x0;
1843 return string;
1847 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1848 GSList *utils_get_config_files(const gchar *subdir)
1850 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1851 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1852 GSList *syslist, *node;
1854 if (!list)
1856 utils_mkdir(path, FALSE);
1858 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1859 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1860 /* merge lists */
1861 list = g_slist_concat(list, syslist);
1863 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1864 /* remove duplicates (next to each other after sorting) */
1865 foreach_slist(node, list)
1867 if (node->next && utils_str_equal(node->next->data, node->data))
1869 GSList *old = node->next;
1871 g_free(old->data);
1872 node->next = old->next;
1873 g_slist_free1(old);
1876 g_free(path);
1877 return list;
1881 /* Suffix can be NULL or a string which should be appended to the Help URL like
1882 * an anchor link, e.g. "#some_anchor". */
1883 gchar *utils_get_help_url(const gchar *suffix)
1885 gchar *uri;
1886 const gchar *uri_file_prefix = utils_get_uri_file_prefix();
1887 gint skip = strlen(uri_file_prefix);
1889 uri = g_strconcat(uri_file_prefix, app->docdir, "/index.html", NULL);
1890 #ifdef G_OS_WIN32
1891 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1892 #endif
1894 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1895 { /* fall back to online documentation if it is not found on the hard disk */
1896 g_free(uri);
1897 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", PACKAGE_VERSION, "/index.html", NULL);
1900 if (suffix != NULL)
1902 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1905 return uri;
1909 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1911 const gchar **p;
1913 for (p = haystack; *p != NULL; ++p)
1915 if (utils_str_equal(*p, needle))
1916 return TRUE;
1918 return FALSE;
1923 * Copies the current environment into a new array.
1924 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1925 * All further arguments are key, value pairs of variables which should be added to
1926 * the environment.
1928 * The argument list must be @c NULL-terminated.
1930 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1931 * @param first_varname Name of the first variable to copy into the new array.
1932 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1934 * @return @transfer{full} The new environment array. Use @c g_strfreev() to free it.
1936 GEANY_API_SYMBOL
1937 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1939 gchar **result;
1940 gchar **p;
1941 gchar **env;
1942 va_list args;
1943 const gchar *key, *value;
1944 guint n, o;
1946 /* count the additional variables */
1947 va_start(args, first_varname);
1948 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1949 va_end(args);
1950 /* the passed arguments should be even (key, value pairs) */
1951 g_return_val_if_fail(o % 2 == 0, NULL);
1953 o /= 2;
1955 /* get all the environ variables */
1956 env = g_listenv();
1958 /* create an array large enough to hold the new environment */
1959 n = g_strv_length(env);
1960 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
1961 result = g_new(gchar *, n + o + 1);
1963 /* copy the environment */
1964 for (n = 0, p = env; *p != NULL; ++p)
1966 /* copy the variable */
1967 value = g_getenv(*p);
1968 if (G_LIKELY(value != NULL))
1970 /* skip excluded variables */
1971 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
1972 continue;
1974 result[n++] = g_strconcat(*p, "=", value, NULL);
1977 g_strfreev(env);
1979 /* now add additional variables */
1980 va_start(args, first_varname);
1981 key = first_varname;
1982 value = va_arg(args, gchar*);
1983 while (key != NULL)
1985 result[n++] = g_strconcat(key, "=", value, NULL);
1987 key = va_arg(args, gchar*);
1988 if (key == NULL)
1989 break;
1990 value = va_arg(args, gchar*);
1992 va_end(args);
1994 result[n] = NULL;
1996 return result;
2000 /* Joins @a first and @a second into a new string vector, freeing the originals.
2001 * The original contents are reused. */
2002 gchar **utils_strv_join(gchar **first, gchar **second)
2004 gchar **strv;
2005 gchar **rptr, **wptr;
2007 if (!first)
2008 return second;
2009 if (!second)
2010 return first;
2012 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2013 wptr = strv;
2015 foreach_strv(rptr, first)
2016 *wptr++ = *rptr;
2017 foreach_strv(rptr, second)
2018 *wptr++ = *rptr;
2020 g_free(first);
2021 g_free(second);
2022 return strv;
2025 /* * Returns the common prefix in a list of strings.
2027 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2029 * @param strv The list of strings to process.
2030 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2032 * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list
2033 * was passed in.
2035 GEANY_EXPORT_SYMBOL
2036 gchar *utils_strv_find_common_prefix(gchar **strv, gssize strv_len)
2038 gsize num;
2040 if (strv_len == 0)
2041 return NULL;
2043 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2045 for (gsize i = 0; strv[0][i]; i++)
2047 for (gsize j = 1; j < num; j++)
2049 if (strv[j][i] != strv[0][i])
2051 /* return prefix on first mismatch */
2052 return g_strndup(strv[0], i);
2057 return g_strdup(strv[0]);
2061 /* * Returns the longest common substring in a list of strings.
2063 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2065 * @param strv The list of strings to process.
2066 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2068 * @return The common prefix that is part of all strings.
2070 GEANY_EXPORT_SYMBOL
2071 gchar *utils_strv_find_lcs(gchar **strv, gssize strv_len, const gchar *delim)
2073 gchar *first, *_sub, *sub;
2074 gsize num;
2075 gsize n_chars;
2076 gsize len;
2077 gsize max = 0;
2078 char *lcs;
2079 gsize found;
2081 if (strv_len == 0)
2082 return NULL;
2084 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2086 first = strv[0];
2087 len = strlen(first);
2089 /* sub is the working area where substrings from first are copied to */
2090 sub = g_malloc(len+1);
2091 lcs = g_strdup("");
2092 foreach_str(_sub, first)
2094 gsize chars_left = len - (_sub - first);
2095 /* No point in continuing if the remainder is too short */
2096 if (max > chars_left)
2097 break;
2098 /* If delimiters are given, we only need to compare substrings which start and
2099 * end with one of them, so skip any non-delim chars at front ... */
2100 if (NZV(delim) && (strchr(delim, _sub[0]) == NULL))
2101 continue;
2102 for (n_chars = 1; n_chars <= chars_left; n_chars++)
2104 if (NZV(delim))
2105 { /* ... and advance to the next delim char at the end, if any */
2106 if (!_sub[n_chars] || strchr(delim, _sub[n_chars]) == NULL)
2107 continue;
2108 n_chars += 1;
2110 g_strlcpy(sub, _sub, n_chars+1);
2111 found = 1;
2112 for (gsize i = 1; i < num; i++)
2114 if (strstr(strv[i], sub) == NULL)
2115 break;
2116 found++;
2118 if (found == num && n_chars > max)
2120 max = n_chars;
2121 SETPTR(lcs, g_strdup(sub));
2125 g_free(sub);
2127 return lcs;
2131 /** Transform file names in a list to be shorter.
2133 * This function takes a list of file names (probably with absolute paths), and
2134 * transforms the paths such that they are short but still unique. This is intended
2135 * for dialogs which present the file list to the user, where the base name may result
2136 * in duplicates (showing the full path might be inappropriate).
2138 * The algorthm strips the common prefix (e-g. the user's home directory) and
2139 * replaces the longest common substring with an ellipsis ("...").
2141 * @param file_names @array{length=file_names_len} The list of strings to process.
2142 * @param file_names_len The number of strings contained in @a file_names. Can be -1 if it's
2143 * terminated by @c NULL.
2144 * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by
2145 @c NULL. Use @c g_strfreev() to free it.
2147 * @since 1.34 (API 239)
2149 GEANY_API_SYMBOL
2150 gchar **utils_strv_shorten_file_list(gchar **file_names, gssize file_names_len)
2152 gsize num;
2153 gsize i;
2154 gchar *prefix, *lcs, *end;
2155 gchar **names;
2156 gsize prefix_len = 0, lcs_len = 0;
2158 if (file_names_len == 0)
2159 return g_new0(gchar *, 1);
2161 g_return_val_if_fail(file_names != NULL, NULL);
2163 num = (file_names_len == -1) ? g_strv_length(file_names) : (gsize) file_names_len;
2164 /* Always include a terminating NULL, enables easy freeing with g_strfreev()
2165 * We just copy the pointers so we can advance them here. But don't
2166 * forget to duplicate the strings before returning.
2168 names = g_new(gchar *, num + 1);
2169 memcpy(names, file_names, num * sizeof(gchar *));
2170 /* Always include a terminating NULL, enables easy freeing with g_strfreev() */
2171 names[num] = NULL;
2173 /* First: determine the common prefix, that will be stripped.
2174 * We only want to strip full path components, including the trailing slash.
2175 * Except if the component is just "/".
2177 prefix = utils_strv_find_common_prefix(names, num);
2178 end = strrchr(prefix, G_DIR_SEPARATOR);
2179 if (end && end > prefix)
2181 prefix_len = end - prefix + 1; /* prefix_len includes the trailing slash */
2182 for (i = 0; i < num; i++)
2183 names[i] += prefix_len;
2186 /* Second: determine the longest common substring (lcs), that will be ellipsized. Again,
2187 * we look only for full path compnents so that we ellipsize between separators. This implies
2188 * that the file name cannot be ellipsized which is desirable anyway.
2190 lcs = utils_strv_find_lcs(names, num, G_DIR_SEPARATOR_S"/");
2191 if (lcs)
2193 lcs_len = strlen(lcs);
2194 /* Don't bother for tiny common parts (which are often just "." or "/"). Beware
2195 * that lcs includes the enclosing dir separators so the part must be at least 5 chars
2196 * to be eligible for ellipsizing.
2198 if (lcs_len < 7)
2199 lcs_len = 0;
2202 /* Last: build the shortened list of unique file names */
2203 for (i = 0; i < num; i++)
2205 if (lcs_len == 0)
2206 { /* no lcs, copy without prefix */
2207 names[i] = g_strdup(names[i]);
2209 else
2211 const gchar *lcs_start = strstr(names[i], lcs);
2212 const gchar *lcs_end = lcs_start + lcs_len;
2213 /* Dir seperators are included in lcs but shouldn't be elipsized. */
2214 names[i] = g_strdup_printf("%.*s...%s", (int)(lcs_start - names[i] + 1), names[i], lcs_end - 1);
2218 g_free(lcs);
2219 g_free(prefix);
2221 return names;
2225 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2226 * obviously g_date_set_parse() uses some magic.
2227 * The returned GDate object must be freed. */
2228 GDate *utils_parse_date(const gchar *input)
2230 GDate *date = g_date_new();
2232 g_date_set_parse(date, input);
2234 if (g_date_valid(date))
2235 return date;
2237 g_date_free(date);
2238 return NULL;
2242 gchar *utils_parse_and_format_build_date(const gchar *input)
2244 gchar date_buf[255];
2245 GDate *date = utils_parse_date(input);
2247 if (date != NULL)
2249 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2250 g_date_free(date);
2251 return g_strdup(date_buf);
2254 return g_strdup(input);
2258 gchar *utils_get_user_config_dir(void)
2260 #ifdef G_OS_WIN32
2261 return win32_get_user_config_dir();
2262 #else
2263 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2264 #endif
2268 static gboolean is_osx_bundle(void)
2270 #ifdef MAC_INTEGRATION
2271 gchar *bundle_id = gtkosx_application_get_bundle_id();
2272 if (bundle_id)
2274 g_free(bundle_id);
2275 return TRUE;
2277 #endif
2278 return FALSE;
2282 const gchar *utils_resource_dir(GeanyResourceDirType type)
2284 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2286 if (!resdirs[RESOURCE_DIR_DATA])
2288 #ifdef G_OS_WIN32
2289 gchar *prefix = win32_get_installation_dir();
2291 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", NULL);
2292 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2293 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2294 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2295 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2296 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2297 g_free(prefix);
2298 #else
2299 if (is_osx_bundle())
2301 # ifdef MAC_INTEGRATION
2302 gchar *prefix = gtkosx_application_get_resource_path();
2304 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2305 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2306 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2307 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2308 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2309 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2310 g_free(prefix);
2311 # endif
2313 else
2315 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2316 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2317 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2318 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2319 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2320 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(GEANY_LIBEXECDIR, "geany", NULL);
2322 #endif
2325 return resdirs[type];
2329 void utils_start_new_geany_instance(const gchar *doc_path)
2331 const gchar *command = is_osx_bundle() ? "open" : "geany";
2332 gchar *exec_path = g_find_program_in_path(command);
2334 if (exec_path)
2336 GError *err = NULL;
2337 const gchar *argv[6]; // max args + 1
2338 gint argc = 0;
2340 argv[argc++] = exec_path;
2341 if (is_osx_bundle())
2343 argv[argc++] = "-n";
2344 argv[argc++] = "-a";
2345 argv[argc++] = "Geany";
2346 argv[argc++] = doc_path;
2348 else
2350 argv[argc++] = "-i";
2351 argv[argc++] = doc_path;
2353 argv[argc] = NULL;
2355 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2357 g_printerr("Unable to open new window: %s\n", err->message);
2358 g_error_free(err);
2360 g_free(exec_path);
2362 else
2363 g_printerr("Unable to find 'geany'\n");
2368 * Get a link-dereferenced, absolute version of a file name.
2370 * This is similar to the POSIX `realpath` function when passed a
2371 * @c NULL argument.
2373 * @warning This function suffers the same problems as the POSIX
2374 * function `realpath()`, namely that it's impossible to determine
2375 * a suitable size for the returned buffer, and so it's limited to a
2376 * maximum of `PATH_MAX`.
2378 * @param file_name The file name to get the real path of.
2380 * @return A newly-allocated string containing the real path which
2381 * should be freed with `g_free()` when no longer needed, or @c NULL
2382 * if the real path cannot be obtained.
2384 * @since 1.32 (API 235)
2386 GEANY_API_SYMBOL
2387 gchar *utils_get_real_path(const gchar *file_name)
2389 return tm_get_real_path(file_name);
2394 * Get a string describing the OS.
2396 * If the OS can be determined, a string which describes the OS will
2397 * be returned. If no OS can be determined then `NULL` will be returned.
2399 * @note The format of the returned string is unspecified and is only
2400 * meant to provide diagnostic information to the user.
2402 * @return A newly-allocated string containing a description of the
2403 * OS if it can be determined or `NULL` if it cannot.
2405 * @since 1.37
2407 gchar *utils_get_os_info_string(void)
2409 gchar *os_info = NULL;
2411 #if GLIB_CHECK_VERSION(2, 64, 0)
2412 # if ! defined(__APPLE__)
2413 /* on non-macOS operating systems */
2415 GString *os_str;
2416 gchar *pretty_name;
2417 gchar *code_name;
2419 pretty_name = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
2420 if (pretty_name == NULL)
2421 return NULL;
2423 os_str = g_string_new(pretty_name);
2424 g_free(pretty_name);
2426 code_name = g_get_os_info(G_OS_INFO_KEY_VERSION_CODENAME);
2427 if (code_name != NULL)
2429 g_string_append_printf(os_str, " (%s)", code_name);
2430 g_free(code_name);
2433 os_info = g_string_free(os_str, FALSE);
2435 # else
2436 /* on macOS, only `G_OS_INFO_KEY_NAME` is supported and returns the
2437 * fixed string `macOS` */
2438 os_info = g_get_os_info(G_OS_INFO_KEY_NAME);
2439 # endif
2440 #else
2441 /* if g_get_os_info() is not available, do it the old-fashioned way */
2442 # if defined(_WIN64)
2443 os_info = g_strdup("Microsoft Windows (64-bit)");
2444 # elif defined(_WIN32)
2445 os_info = g_strdup("Microsoft Windows");
2446 # elif defined(__APPLE__)
2447 os_info = g_strdup("Apple macOS");
2448 # elif defined(__linux__)
2449 os_info = g_strdup("Linux");
2450 # elif defined(__FreeBSD__)
2451 os_info = g_strdup("FreeBSD");
2452 # elif defined(__ANDROID__)
2453 os_info = g_strdup("Android");
2454 # endif
2455 #endif
2457 return os_info;