Sync ctags parsers with small changes with Geany (#2990)
[geany-mirror.git] / src / utils.c
blob6f7cb5b08f77614e8bf78ae5616a533856fe5bd5
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 set in the preferences dialog is used. In case
68 * that fails or it is unset, the user is asked to correct or fill it.
70 * @param uri The URI to open in the web browser.
72 * @since 0.16
73 **/
74 GEANY_API_SYMBOL
75 void utils_open_browser(const gchar *uri)
77 #ifdef G_OS_WIN32
78 g_return_if_fail(uri != NULL);
79 win32_open_browser(uri);
80 #else
81 gchar *argv[2] = { (gchar *) uri, NULL };
83 g_return_if_fail(uri != NULL);
85 while (!spawn_async(NULL, tool_prefs.browser_cmd, argv, NULL, NULL, NULL))
87 gchar *new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window),
88 _("Failed to spawn the configured browser command. "
89 "Please correct it or enter another one."),
90 tool_prefs.browser_cmd);
92 if (new_cmd == NULL) /* user canceled */
93 break;
95 SETPTR(tool_prefs.browser_cmd, new_cmd);
97 #endif
101 /* taken from anjuta, to determine the EOL mode of the file */
102 gint utils_get_line_endings(const gchar* buffer, gsize size)
104 gsize i;
105 guint cr, lf, crlf, max_mode;
106 gint mode;
108 cr = lf = crlf = 0;
110 for (i = 0; i < size ; i++)
112 if (buffer[i] == 0x0a)
114 /* LF */
115 lf++;
117 else if (buffer[i] == 0x0d)
119 if (i >= (size - 1))
121 /* Last char, CR */
122 cr++;
124 else
126 if (buffer[i + 1] != 0x0a)
128 /* CR */
129 cr++;
131 else
133 /* CRLF */
134 crlf++;
136 i++;
141 /* Vote for the maximum */
142 mode = SC_EOL_LF;
143 max_mode = lf;
144 if (crlf > max_mode)
146 mode = SC_EOL_CRLF;
147 max_mode = crlf;
149 if (cr > max_mode)
151 mode = SC_EOL_CR;
152 max_mode = cr;
155 return mode;
159 gboolean utils_isbrace(gchar c, gboolean include_angles)
161 switch (c)
163 case '<':
164 case '>':
165 return include_angles;
167 case '(':
168 case ')':
169 case '{':
170 case '}':
171 case '[':
172 case ']': return TRUE;
173 default: return FALSE;
178 gboolean utils_is_opening_brace(gchar c, gboolean include_angles)
180 switch (c)
182 case '<':
183 return include_angles;
185 case '(':
186 case '{':
187 case '[': return TRUE;
188 default: return FALSE;
194 * Writes @a text into a file named @a filename.
195 * If the file doesn't exist, it will be created.
196 * If it already exists, it will be overwritten.
198 * @warning You should use @c g_file_set_contents() instead if you don't need
199 * file permissions and other metadata to be preserved, as that always handles
200 * disk exhaustion safely.
202 * @param filename The filename of the file to write, in locale encoding.
203 * @param text The text to write into the file.
205 * @return 0 if the file was successfully written, otherwise the @c errno of the
206 * failed operation is returned.
208 GEANY_API_SYMBOL
209 gint utils_write_file(const gchar *filename, const gchar *text)
211 g_return_val_if_fail(filename != NULL, ENOENT);
212 g_return_val_if_fail(text != NULL, EINVAL);
214 if (file_prefs.use_safe_file_saving)
216 GError *error = NULL;
217 if (! g_file_set_contents(filename, text, -1, &error))
219 geany_debug("%s: could not write to file %s (%s)", G_STRFUNC, filename, error->message);
220 g_error_free(error);
221 return EIO;
224 else
226 FILE *fp;
227 gsize bytes_written, len;
228 gboolean fail = FALSE;
230 if (filename == NULL)
231 return ENOENT;
233 len = strlen(text);
234 errno = 0;
235 fp = g_fopen(filename, "w");
236 if (fp == NULL)
237 fail = TRUE;
238 else
240 bytes_written = fwrite(text, sizeof(gchar), len, fp);
242 if (len != bytes_written)
244 fail = TRUE;
245 geany_debug(
246 "utils_write_file(): written only %"G_GSIZE_FORMAT" bytes, had to write %"G_GSIZE_FORMAT" bytes to %s",
247 bytes_written, len, filename);
249 if (fclose(fp) != 0)
250 fail = TRUE;
252 if (fail)
254 geany_debug("utils_write_file(): could not write to file %s (%s)",
255 filename, g_strerror(errno));
256 return FALLBACK(errno, EIO);
259 return 0;
263 /** Searches backward through @a size bytes looking for a '<'.
264 * @param sel .
265 * @param size .
266 * @return @nullable The tag name (newly allocated) or @c NULL if no opening tag was found.
268 GEANY_API_SYMBOL
269 gchar *utils_find_open_xml_tag(const gchar sel[], gint size)
271 const gchar *cur, *begin;
272 gsize len;
274 cur = utils_find_open_xml_tag_pos(sel, size);
275 if (cur == NULL)
276 return NULL;
278 cur++; /* skip the bracket */
279 begin = cur;
280 while (strchr(":_-.", *cur) || isalnum(*cur))
281 cur++;
283 len = (gsize)(cur - begin);
284 return len ? g_strndup(begin, len) : NULL;
288 /** Searches backward through @a size bytes looking for a '<'.
289 * @param sel .
290 * @param size .
291 * @return @nullable pointer to '<' of the found opening tag within @a sel, or @c NULL if no opening tag was found.
293 GEANY_API_SYMBOL
294 const gchar *utils_find_open_xml_tag_pos(const gchar sel[], gint size)
296 /* stolen from anjuta and modified */
297 const gchar *begin, *cur;
299 if (G_UNLIKELY(size < 3))
300 { /* Smallest tag is "<p>" which is 3 characters */
301 return NULL;
303 begin = &sel[0];
304 cur = &sel[size - 1];
306 /* Skip to the character before the closing brace */
307 while (cur > begin)
309 if (*cur == '>')
310 break;
311 --cur;
313 --cur;
314 /* skip whitespace */
315 while (cur > begin && isspace(*cur))
316 cur--;
317 if (*cur == '/')
318 return NULL; /* we found a short tag which doesn't need to be closed */
319 while (cur > begin)
321 if (*cur == '<')
322 break;
323 /* exit immediately if such non-valid XML/HTML is detected, e.g. "<script>if a >" */
324 else if (*cur == '>')
325 break;
326 --cur;
329 /* if the found tag is an opening, not a closing tag or empty <> */
330 if (*cur == '<' && *(cur + 1) != '/' && *(cur + 1) != '>')
331 return cur;
333 return NULL;
337 /* Returns true if tag_name is a self-closing tag */
338 gboolean utils_is_short_html_tag(const gchar *tag_name)
340 const gchar names[][20] = {
341 "area",
342 "base",
343 "basefont", /* < or not < */
344 "br",
345 "col",
346 "command",
347 "embed",
348 "frame",
349 "hr",
350 "img",
351 "input",
352 "keygen",
353 "link",
354 "meta",
355 "param",
356 "source",
357 "track",
358 "wbr"
361 if (tag_name)
363 if (bsearch(tag_name, names, G_N_ELEMENTS(names), 20,
364 (GCompareFunc)g_ascii_strcasecmp))
365 return TRUE;
367 return FALSE;
371 const gchar *utils_get_eol_name(gint eol_mode)
373 switch (eol_mode)
375 case SC_EOL_CRLF: return _("Windows (CRLF)"); break;
376 case SC_EOL_CR: return _("Classic Mac (CR)"); break;
377 default: return _("Unix (LF)"); break;
382 const gchar *utils_get_eol_short_name(gint eol_mode)
384 switch (eol_mode)
386 case SC_EOL_CRLF: return _("CRLF"); break;
387 case SC_EOL_CR: return _("CR"); break;
388 default: return _("LF"); break;
393 const gchar *utils_get_eol_char(gint eol_mode)
395 switch (eol_mode)
397 case SC_EOL_CRLF: return "\r\n"; break;
398 case SC_EOL_CR: return "\r"; break;
399 default: return "\n"; break;
404 /* Converts line endings to @a target_eol_mode. */
405 void utils_ensure_same_eol_characters(GString *string, gint target_eol_mode)
407 const gchar *eol_str = utils_get_eol_char(target_eol_mode);
409 /* first convert data to LF only */
410 utils_string_replace_all(string, "\r\n", "\n");
411 utils_string_replace_all(string, "\r", "\n");
413 if (target_eol_mode == SC_EOL_LF)
414 return;
416 /* now convert to desired line endings */
417 utils_string_replace_all(string, "\n", eol_str);
421 gboolean utils_atob(const gchar *str)
423 if (G_UNLIKELY(str == NULL))
424 return FALSE;
425 else if (strcmp(str, "TRUE") == 0 || strcmp(str, "true") == 0)
426 return TRUE;
427 return FALSE;
431 /* NULL-safe version of g_path_is_absolute(). */
432 gboolean utils_is_absolute_path(const gchar *path)
434 if (G_UNLIKELY(EMPTY(path)))
435 return FALSE;
437 return g_path_is_absolute(path);
441 /* Skips root if path is absolute, do nothing otherwise.
442 * This is a relative-safe version of g_path_skip_root().
444 const gchar *utils_path_skip_root(const gchar *path)
446 const gchar *path_relative;
448 path_relative = g_path_skip_root(path);
450 return (path_relative != NULL) ? path_relative : path;
454 /* Convert a fractional @a val in the range [0, 1] to a whole value in the range [0, @a factor].
455 * In particular, this is used for converting a @c GdkColor to the "#RRGGBB" format in a way that
456 * agrees with GTK+, so the "#RRGGBB" in the color picker is the same "#RRGGBB" that is inserted
457 * into the document. See https://github.com/geany/geany/issues/1527
459 gdouble utils_scale_round(gdouble val, gdouble factor)
461 val = floor(val * factor + 0.5);
462 val = MAX(val, 0);
463 val = MIN(val, factor);
465 return val;
469 /* like g_utf8_strdown() but if @str is not valid UTF8, convert it from locale first.
470 * returns NULL on charset conversion failure */
471 static gchar *utf8_strdown(const gchar *str)
473 gchar *down;
475 if (g_utf8_validate(str, -1, NULL))
476 down = g_utf8_strdown(str, -1);
477 else
479 down = g_locale_to_utf8(str, -1, NULL, NULL, NULL);
480 if (down)
481 SETPTR(down, g_utf8_strdown(down, -1));
484 return down;
489 * A replacement function for g_strncasecmp() to compare strings case-insensitive.
490 * It converts both strings into lowercase using g_utf8_strdown() and then compare
491 * both strings using strcmp().
492 * This is not completely accurate regarding locale-specific case sorting rules
493 * but seems to be a good compromise between correctness and performance.
495 * The input strings should be in UTF-8 or locale encoding.
497 * @param s1 @nullable Pointer to first string or @c NULL.
498 * @param s2 @nullable Pointer to second string or @c NULL.
500 * @return an integer less than, equal to, or greater than zero if @a s1 is found, respectively,
501 * to be less than, to match, or to be greater than @a s2.
503 * @since 0.16
505 GEANY_API_SYMBOL
506 gint utils_str_casecmp(const gchar *s1, const gchar *s2)
508 gchar *tmp1, *tmp2;
509 gint result;
511 g_return_val_if_fail(s1 != NULL, 1);
512 g_return_val_if_fail(s2 != NULL, -1);
514 /* ensure strings are UTF-8 and lowercase */
515 tmp1 = utf8_strdown(s1);
516 if (! tmp1)
517 return 1;
518 tmp2 = utf8_strdown(s2);
519 if (! tmp2)
521 g_free(tmp1);
522 return -1;
525 /* compare */
526 result = strcmp(tmp1, tmp2);
528 g_free(tmp1);
529 g_free(tmp2);
530 return result;
535 * Truncates the input string to a given length.
536 * Characters are removed from the middle of the string, so the start and the end of string
537 * won't change.
539 * @param string Input string.
540 * @param truncate_length The length in characters of the resulting string.
542 * @return A copy of @a string which is truncated to @a truncate_length characters,
543 * should be freed when no longer needed.
545 * @since 0.17
547 /* This following function is taken from Gedit. */
548 GEANY_API_SYMBOL
549 gchar *utils_str_middle_truncate(const gchar *string, guint truncate_length)
551 GString *truncated;
552 guint length;
553 guint n_chars;
554 guint num_left_chars;
555 guint right_offset;
556 guint delimiter_length;
557 const gchar *delimiter = "\342\200\246";
559 g_return_val_if_fail(string != NULL, NULL);
561 length = strlen(string);
563 g_return_val_if_fail(g_utf8_validate(string, length, NULL), NULL);
565 /* It doesn't make sense to truncate strings to less than the size of the delimiter plus 2
566 * characters (one on each side) */
567 delimiter_length = g_utf8_strlen(delimiter, -1);
568 if (truncate_length < (delimiter_length + 2))
569 return g_strdup(string);
571 n_chars = g_utf8_strlen(string, length);
573 /* Make sure the string is not already small enough. */
574 if (n_chars <= truncate_length)
575 return g_strdup (string);
577 /* Find the 'middle' where the truncation will occur. */
578 num_left_chars = (truncate_length - delimiter_length) / 2;
579 right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
581 truncated = g_string_new_len(string, g_utf8_offset_to_pointer(string, num_left_chars) - string);
582 g_string_append(truncated, delimiter);
583 g_string_append(truncated, g_utf8_offset_to_pointer(string, right_offset));
585 return g_string_free(truncated, FALSE);
590 * @c NULL-safe string comparison. Returns @c TRUE if both @a a and @a b are @c NULL
591 * or if @a a and @a b refer to valid strings which are equal.
593 * @param a @nullable Pointer to first string or @c NULL.
594 * @param b @nullable Pointer to second string or @c NULL.
596 * @return @c TRUE if @a a equals @a b, else @c FALSE.
598 GEANY_API_SYMBOL
599 gboolean utils_str_equal(const gchar *a, const gchar *b)
601 /* (taken from libexo from os-cillation) */
602 if (a == NULL && b == NULL) return TRUE;
603 else if (a == NULL || b == NULL) return FALSE;
605 return strcmp(a, b) == 0;
610 * Removes the extension from @a filename and return the result in a newly allocated string.
612 * @param filename The filename to operate on.
614 * @return A newly-allocated string, should be freed when no longer needed.
616 GEANY_API_SYMBOL
617 gchar *utils_remove_ext_from_filename(const gchar *filename)
619 gchar *last_dot;
620 gchar *result;
621 gsize len;
623 g_return_val_if_fail(filename != NULL, NULL);
625 last_dot = strrchr(filename, '.');
626 if (! last_dot)
627 return g_strdup(filename);
629 len = (gsize) (last_dot - filename);
630 result = g_malloc(len + 1);
631 memcpy(result, filename, len);
632 result[len] = 0;
634 return result;
638 gchar utils_brace_opposite(gchar ch)
640 switch (ch)
642 case '(': return ')';
643 case ')': return '(';
644 case '[': return ']';
645 case ']': return '[';
646 case '{': return '}';
647 case '}': return '{';
648 case '<': return '>';
649 case '>': return '<';
650 default: return '\0';
655 /* Checks whether the given file can be written. locale_filename is expected in locale encoding.
656 * Returns 0 if it can be written, otherwise it returns errno */
657 gint utils_is_file_writable(const gchar *locale_filename)
659 gchar *file;
660 gint ret;
662 if (! g_file_test(locale_filename, G_FILE_TEST_EXISTS) &&
663 ! g_file_test(locale_filename, G_FILE_TEST_IS_DIR))
664 /* get the file's directory to check for write permission if it doesn't yet exist */
665 file = g_path_get_dirname(locale_filename);
666 else
667 file = g_strdup(locale_filename);
669 #ifdef G_OS_WIN32
670 /* use _waccess on Windows, access() doesn't accept special characters */
671 ret = win32_check_write_permission(file);
672 #else
674 /* access set also errno to "FILE NOT FOUND" even if locale_filename is writeable, so use
675 * errno only when access() explicitly returns an error */
676 if (access(file, R_OK | W_OK) != 0)
677 ret = errno;
678 else
679 ret = 0;
680 #endif
681 g_free(file);
682 return ret;
686 /* Replaces all occurrences of needle in haystack with replacement.
687 * Warning: *haystack must be a heap address; it may be freed and reassigned.
688 * Note: utils_string_replace_all() will always be faster when @a replacement is longer
689 * than @a needle.
690 * All strings have to be NULL-terminated.
691 * See utils_string_replace_all() for details. */
692 void utils_str_replace_all(gchar **haystack, const gchar *needle, const gchar *replacement)
694 GString *str;
696 g_return_if_fail(*haystack != NULL);
698 str = g_string_new(*haystack);
700 g_free(*haystack);
701 utils_string_replace_all(str, needle, replacement);
703 *haystack = g_string_free(str, FALSE);
707 gint utils_strpos(const gchar *haystack, const gchar *needle)
709 const gchar *sub;
711 if (! *needle)
712 return -1;
714 sub = strstr(haystack, needle);
715 if (! sub)
716 return -1;
718 return sub - haystack;
723 * Retrieves a formatted date/time string from strftime().
724 * This function should be preferred to directly calling strftime() since this function
725 * works on UTF-8 encoded strings.
727 * @param format The format string to pass to strftime(3). See the strftime(3)
728 * documentation for details, in UTF-8 encoding.
729 * @param time_to_use @nullable The date/time to use, in time_t format or @c NULL to use the current time.
731 * @return A newly-allocated string, should be freed when no longer needed.
733 * @since 0.16
735 GEANY_API_SYMBOL
736 gchar *utils_get_date_time(const gchar *format, time_t *time_to_use)
738 const struct tm *tm;
739 static gchar date[1024];
740 gchar *locale_format;
741 gsize len;
743 g_return_val_if_fail(format != NULL, NULL);
745 if (! g_utf8_validate(format, -1, NULL))
747 locale_format = g_locale_from_utf8(format, -1, NULL, NULL, NULL);
748 if (locale_format == NULL)
749 return NULL;
751 else
752 locale_format = g_strdup(format);
754 if (time_to_use != NULL)
755 tm = localtime(time_to_use);
756 else
758 time_t tp = time(NULL);
759 tm = localtime(&tp);
762 len = strftime(date, 1024, locale_format, tm);
763 g_free(locale_format);
764 if (len == 0)
765 return NULL;
767 if (! g_utf8_validate(date, len, NULL))
768 return g_locale_to_utf8(date, len, NULL, NULL, NULL);
769 else
770 return g_strdup(date);
774 gchar *utils_get_initials(const gchar *name)
776 gint i = 1, j = 1;
777 gchar *initials = g_malloc0(5);
779 initials[0] = name[0];
780 while (name[i] != '\0' && j < 4)
782 if (name[i] == ' ' && name[i + 1] != ' ')
784 initials[j++] = name[i + 1];
786 i++;
788 return initials;
793 * Wraps g_key_file_get_integer() to add a default value argument.
795 * @param config A GKeyFile object.
796 * @param section The group name to look in for the key.
797 * @param key The key to find.
798 * @param default_value The default value which will be returned when @a section or @a key
799 * don't exist.
801 * @return The value associated with @a key as an integer, or the given default value if the value
802 * could not be retrieved.
804 GEANY_API_SYMBOL
805 gint utils_get_setting_integer(GKeyFile *config, const gchar *section, const gchar *key,
806 const gint default_value)
808 gint tmp;
809 GError *error = NULL;
811 g_return_val_if_fail(config, default_value);
813 tmp = g_key_file_get_integer(config, section, key, &error);
814 if (error)
816 g_error_free(error);
817 return default_value;
819 return tmp;
824 * Wraps g_key_file_get_boolean() to add a default value argument.
826 * @param config A GKeyFile object.
827 * @param section The group name to look in for the key.
828 * @param key The key to find.
829 * @param default_value The default value which will be returned when @c section or @c key
830 * don't exist.
832 * @return The value associated with @a key as a boolean, or the given default value if the value
833 * could not be retrieved.
835 GEANY_API_SYMBOL
836 gboolean utils_get_setting_boolean(GKeyFile *config, const gchar *section, const gchar *key,
837 const gboolean default_value)
839 gboolean tmp;
840 GError *error = NULL;
842 g_return_val_if_fail(config, default_value);
844 tmp = g_key_file_get_boolean(config, section, key, &error);
845 if (error)
847 g_error_free(error);
848 return default_value;
850 return tmp;
855 * Wraps g_key_file_get_double() to add a default value argument.
857 * @param config A GKeyFile object.
858 * @param section The group name to look in for the key.
859 * @param key The key to find.
860 * @param default_value The default value which will be returned when @a section or @a key
861 * don't exist.
863 * @return The value associated with @a key as an integer, or the given default value if the value
864 * could not be retrieved.
866 GEANY_API_SYMBOL
867 gdouble utils_get_setting_double(GKeyFile *config, const gchar *section, const gchar *key,
868 const gdouble default_value)
870 gdouble tmp;
871 GError *error = NULL;
873 g_return_val_if_fail(config, default_value);
875 tmp = g_key_file_get_double(config, section, key, &error);
876 if (error)
878 g_error_free(error);
879 return default_value;
881 return tmp;
886 * Wraps g_key_file_get_string() to add a default value argument.
888 * @param config A GKeyFile object.
889 * @param section The group name to look in for the key.
890 * @param key The key to find.
891 * @param default_value The default value which will be returned when @a section or @a key
892 * don't exist.
894 * @return A newly allocated string, either the value for @a key or a copy of the given
895 * default value if it could not be retrieved.
897 GEANY_API_SYMBOL
898 gchar *utils_get_setting_string(GKeyFile *config, const gchar *section, const gchar *key,
899 const gchar *default_value)
901 gchar *tmp;
903 g_return_val_if_fail(config, g_strdup(default_value));
905 tmp = g_key_file_get_string(config, section, key, NULL);
906 if (!tmp)
908 return g_strdup(default_value);
910 return tmp;
914 gchar *utils_get_hex_from_color(GdkColor *color)
916 g_return_val_if_fail(color != NULL, NULL);
918 return g_strdup_printf("#%02X%02X%02X",
919 (guint) (utils_scale_round(color->red / 65535.0, 255)),
920 (guint) (utils_scale_round(color->green / 65535.0, 255)),
921 (guint) (utils_scale_round(color->blue / 65535.0, 255)));
925 /* Get directory from current file in the notebook.
926 * Returns dir string that should be freed or NULL, depending on whether current file is valid.
927 * Returned string is in UTF-8 encoding */
928 gchar *utils_get_current_file_dir_utf8(void)
930 GeanyDocument *doc = document_get_current();
932 if (doc != NULL)
934 /* get current filename */
935 const gchar *cur_fname = doc->file_name;
937 if (cur_fname != NULL)
939 /* get folder part from current filename */
940 return g_path_get_dirname(cur_fname); /* returns "." if no path */
944 return NULL; /* no file open */
948 /* very simple convenience function */
949 void utils_beep(void)
951 if (prefs.beep_on_errors)
952 gdk_beep();
956 /* taken from busybox, thanks */
957 gchar *utils_make_human_readable_str(guint64 size, gulong block_size,
958 gulong display_unit)
960 /* The code will adjust for additional (appended) units. */
961 static const gchar zero_and_units[] = { '0', 0, 'K', 'M', 'G', 'T' };
962 static const gchar fmt[] = "%Lu %c%c";
963 static const gchar fmt_tenths[] = "%Lu.%d %c%c";
965 guint64 val;
966 gint frac;
967 const gchar *u;
968 const gchar *f;
970 u = zero_and_units;
971 f = fmt;
972 frac = 0;
974 val = size * block_size;
975 if (val == 0)
976 return g_strdup(u);
978 if (display_unit)
980 val += display_unit/2; /* Deal with rounding. */
981 val /= display_unit; /* Don't combine with the line above!!! */
983 else
985 ++u;
986 while ((val >= 1024) && (u < zero_and_units + sizeof(zero_and_units) - 1))
988 f = fmt_tenths;
989 ++u;
990 frac = ((((gint)(val % 1024)) * 10) + (1024 / 2)) / 1024;
991 val /= 1024;
993 if (frac >= 10)
994 { /* We need to round up here. */
995 ++val;
996 frac = 0;
1000 /* If f==fmt then 'frac' and 'u' are ignored. */
1001 return g_strdup_printf(f, val, frac, *u, 'b');
1005 /* converts a color representation using gdk_color_parse(), with additional
1006 * support of the "0x" prefix as a synonym for "#" */
1007 gboolean utils_parse_color(const gchar *spec, GdkColor *color)
1009 gchar buf[64] = {0};
1011 g_return_val_if_fail(spec != NULL, -1);
1013 if (spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X'))
1015 /* convert to # format for GDK to understand it */
1016 buf[0] = '#';
1017 strncpy(buf + 1, spec + 2, sizeof(buf) - 2);
1018 spec = buf;
1021 return gdk_color_parse(spec, color);
1025 /* converts a GdkColor to the packed 24 bits BGR format, as understood by Scintilla
1026 * returns a 24 bits BGR color, or -1 on failure */
1027 gint utils_color_to_bgr(const GdkColor *c)
1029 g_return_val_if_fail(c != NULL, -1);
1030 return (c->red / 256) | ((c->green / 256) << 8) | ((c->blue / 256) << 16);
1034 /* parses @p spec using utils_parse_color() and convert it to 24 bits BGR using
1035 * utils_color_to_bgr() */
1036 gint utils_parse_color_to_bgr(const gchar *spec)
1038 GdkColor color;
1039 if (utils_parse_color(spec, &color))
1040 return utils_color_to_bgr(&color);
1041 else
1042 return -1;
1046 /* Returns: newly allocated string with the current time formatted HH:MM:SS.
1047 * If "include_microseconds" is TRUE, microseconds are appended.
1049 * The returned string should be freed with g_free(). */
1050 gchar *utils_get_current_time_string(gboolean include_microseconds)
1052 GDateTime *now = g_date_time_new_now_local();
1053 const gchar *format = include_microseconds ? "%H:%M:%S.%f" : "%H:%M:%S";
1054 gchar *time_string = g_date_time_format(now, format);
1055 g_date_time_unref(now);
1056 return time_string;
1060 GIOChannel *utils_set_up_io_channel(
1061 gint fd, GIOCondition cond, gboolean nblock, GIOFunc func, gpointer data)
1063 GIOChannel *ioc;
1064 /*const gchar *encoding;*/
1066 #ifdef G_OS_WIN32
1067 ioc = g_io_channel_win32_new_fd(fd);
1068 #else
1069 ioc = g_io_channel_unix_new(fd);
1070 #endif
1072 if (nblock)
1073 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
1075 g_io_channel_set_encoding(ioc, NULL, NULL);
1077 if (! g_get_charset(&encoding))
1078 { // hope this works reliably
1079 GError *error = NULL;
1080 g_io_channel_set_encoding(ioc, encoding, &error);
1081 if (error)
1083 geany_debug("%s: %s", G_STRFUNC, error->message);
1084 g_error_free(error);
1085 return ioc;
1089 /* "auto-close" ;-) */
1090 g_io_channel_set_close_on_unref(ioc, TRUE);
1092 g_io_add_watch(ioc, cond, func, data);
1093 g_io_channel_unref(ioc);
1095 return ioc;
1099 /* Contributed by Stefan Oltmanns, thanks.
1100 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1101 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1102 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1104 gsize i, j, len;
1105 guint unicodechar;
1107 g_return_val_if_fail(string != NULL, FALSE);
1109 j = 0;
1110 len = strlen(string);
1111 for (i = 0; i < len; i++)
1113 if (string[i]=='\\')
1115 if (i++ >= strlen(string))
1117 return FALSE;
1119 switch (string[i])
1121 case '\\':
1122 if (keep_backslash)
1123 string[j++] = '\\';
1124 string[j] = '\\';
1125 break;
1126 case 'n':
1127 string[j] = '\n';
1128 break;
1129 case 'r':
1130 string[j] = '\r';
1131 break;
1132 case 't':
1133 string[j] = '\t';
1134 break;
1135 #if 0
1136 case 'x': /* Warning: May produce illegal utf-8 string! */
1137 i += 2;
1138 if (i >= strlen(string))
1140 return FALSE;
1142 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1143 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1144 else return FALSE;
1145 string[j] <<= 4;
1146 if (isdigit(string[i])) string[j] |= string[i] - 48;
1147 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1148 else return FALSE;
1149 break;
1150 #endif
1151 case 'u':
1153 i += 2;
1154 if (i >= strlen(string))
1156 return FALSE;
1158 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1159 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1160 else return FALSE;
1161 unicodechar <<= 4;
1162 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1163 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1164 else return FALSE;
1165 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1166 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1168 i += 2;
1169 unicodechar <<= 8;
1170 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1171 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1172 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1173 else unicodechar |= tolower(string[i])-87;
1175 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1176 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1178 i += 2;
1179 unicodechar <<= 8;
1180 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1181 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1182 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1183 else unicodechar |= tolower(string[i])-87;
1185 if (unicodechar < 0x80)
1187 string[j] = unicodechar;
1189 else if (unicodechar < 0x800)
1191 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1192 j++;
1193 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1195 else if (unicodechar < 0x10000)
1197 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1198 j++;
1199 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1200 j++;
1201 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1203 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1205 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1206 j++;
1207 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1208 j++;
1209 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1210 j++;
1211 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1213 else
1215 return FALSE;
1217 break;
1219 default:
1220 /* unnecessary escapes are allowed */
1221 if (keep_backslash)
1222 string[j++] = '\\';
1223 string[j] = string[i];
1226 else
1228 string[j] = string[i];
1230 j++;
1232 while (j < i)
1234 string[j] = 0;
1235 j++;
1237 return TRUE;
1241 /* Wraps a string in place, replacing a space with a newline character.
1242 * wrapstart is the minimum position to start wrapping or -1 for default */
1243 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1245 gchar *pos, *linestart;
1246 gboolean ret = FALSE;
1248 if (wrapstart < 0)
1249 wrapstart = 80;
1251 for (pos = linestart = string; *pos != '\0'; pos++)
1253 if (pos - linestart >= wrapstart && *pos == ' ')
1255 *pos = '\n';
1256 linestart = pos;
1257 ret = TRUE;
1260 return ret;
1265 * Converts the given UTF-8 encoded string into locale encoding.
1266 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1268 * @param utf8_text UTF-8 encoded text.
1270 * @return The converted string in locale encoding, or a copy of the input string if conversion
1271 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1273 GEANY_API_SYMBOL
1274 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1276 #ifdef G_OS_WIN32
1277 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1278 * which would result in wrongly converted strings */
1279 return g_strdup(utf8_text);
1280 #else
1281 gchar *locale_text;
1283 if (! utf8_text)
1284 return NULL;
1285 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1286 if (locale_text == NULL)
1287 locale_text = g_strdup(utf8_text);
1288 return locale_text;
1289 #endif
1294 * Converts the given string (in locale encoding) into UTF-8 encoding.
1295 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1297 * @param locale_text Text in locale encoding.
1299 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1300 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1302 GEANY_API_SYMBOL
1303 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1305 #ifdef G_OS_WIN32
1306 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1307 * which would result in wrongly converted strings */
1308 return g_strdup(locale_text);
1309 #else
1310 gchar *utf8_text;
1312 if (! locale_text)
1313 return NULL;
1314 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1315 if (utf8_text == NULL)
1316 utf8_text = g_strdup(locale_text);
1317 return utf8_text;
1318 #endif
1322 /* Pass pointers to free after arg_count.
1323 * The last argument must be NULL as an extra check that arg_count is correct. */
1324 void utils_free_pointers(gsize arg_count, ...)
1326 va_list a;
1327 gsize i;
1328 gpointer ptr;
1330 va_start(a, arg_count);
1331 for (i = 0; i < arg_count; i++)
1333 ptr = va_arg(a, gpointer);
1334 g_free(ptr);
1336 ptr = va_arg(a, gpointer);
1337 if (ptr)
1338 g_warning("Wrong arg_count!");
1339 va_end(a);
1343 /* Creates a string array deep copy of a series of non-NULL strings.
1344 * The first argument is nothing special and must not be NULL.
1345 * The list must be terminated with NULL. */
1346 GEANY_EXPORT_SYMBOL
1347 gchar **utils_strv_new(const gchar *first, ...)
1349 gsize strvlen, i;
1350 va_list args;
1351 gchar *str;
1352 gchar **strv;
1354 g_return_val_if_fail(first != NULL, NULL);
1356 strvlen = 1; /* for first argument */
1358 /* count other arguments */
1359 va_start(args, first);
1360 for (; va_arg(args, gchar*) != NULL; strvlen++);
1361 va_end(args);
1363 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1364 strv[0] = g_strdup(first);
1366 va_start(args, first);
1367 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1369 strv[i] = g_strdup(str);
1371 va_end(args);
1373 strv[i] = NULL;
1374 return strv;
1379 * Creates a directory if it doesn't already exist.
1380 * Creates intermediate parent directories as needed, too.
1381 * The permissions of the created directory are set 0700.
1383 * @param path The path of the directory to create, in locale encoding.
1384 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1386 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1387 * failed operation is returned.
1389 GEANY_API_SYMBOL
1390 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1392 gint mode = 0700;
1393 gint result;
1395 if (path == NULL || strlen(path) == 0)
1396 return EFAULT;
1398 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1399 if (result != 0)
1400 return errno;
1401 return 0;
1406 * Gets a list of files from the specified directory.
1407 * Locale encoding is expected for @a path and used for the file list. The list and the data
1408 * in the list should be freed after use, e.g.:
1409 * @code
1410 * g_slist_foreach(list, (GFunc) g_free, NULL);
1411 * g_slist_free(list); @endcode
1413 * @note If you don't need a list you should use the foreach_dir() macro instead -
1414 * it's more efficient.
1416 * @param path The path of the directory to scan, in locale encoding.
1417 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1418 * will use more memory.
1419 * @param sort Whether to sort alphabetically (UTF-8 safe).
1420 * @param error The location for storing a possible error, or @c NULL.
1422 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL if
1423 * no files were found. The list and its data should be freed when no longer needed.
1424 * @see utils_get_file_list().
1426 GEANY_API_SYMBOL
1427 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1429 GSList *list = NULL;
1430 GDir *dir;
1431 const gchar *filename;
1433 if (error)
1434 *error = NULL;
1435 g_return_val_if_fail(path != NULL, NULL);
1437 dir = g_dir_open(path, 0, error);
1438 if (dir == NULL)
1439 return NULL;
1441 foreach_dir(filename, dir)
1443 list = g_slist_prepend(list, full_path ?
1444 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1446 g_dir_close(dir);
1447 /* sorting last is quicker than on insertion */
1448 if (sort)
1449 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1450 return list;
1455 * Gets a sorted list of files from the specified directory.
1456 * Locale encoding is expected for @a path and used for the file list. The list and the data
1457 * in the list should be freed after use, e.g.:
1458 * @code
1459 * g_slist_foreach(list, (GFunc) g_free, NULL);
1460 * g_slist_free(list); @endcode
1462 * @param path The path of the directory to scan, in locale encoding.
1463 * @param length The location to store the number of non-@c NULL data items in the list,
1464 * unless @c NULL.
1465 * @param error The location for storing a possible error, or @c NULL.
1467 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL
1468 * if no files were found. The list and its data should be freed when no longer needed.
1469 * @see utils_get_file_list_full().
1471 GEANY_API_SYMBOL
1472 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1474 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1476 if (length)
1477 *length = g_slist_length(list);
1478 return list;
1482 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1483 gboolean utils_str_has_upper(const gchar *str)
1485 gunichar c;
1487 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1488 return FALSE;
1490 while (*str != '\0')
1492 c = g_utf8_get_char(str);
1493 /* check only letters and stop once the first non-capital was found */
1494 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1495 return TRUE;
1496 /* FIXME don't write a const string */
1497 str = g_utf8_next_char(str);
1499 return FALSE;
1503 /* end can be -1 for haystack->len.
1504 * returns: position of found text or -1. */
1505 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1507 gint pos;
1509 g_return_val_if_fail(haystack != NULL, -1);
1510 if (haystack->len == 0)
1511 return -1;
1513 g_return_val_if_fail(start >= 0, -1);
1514 if (start >= (gint)haystack->len)
1515 return -1;
1517 g_return_val_if_fail(!EMPTY(needle), -1);
1519 if (end < 0)
1520 end = haystack->len;
1522 pos = utils_strpos(haystack->str + start, needle);
1523 if (pos == -1)
1524 return -1;
1526 pos += start;
1527 if (pos >= end)
1528 return -1;
1529 return pos;
1533 /* Replaces @len characters from offset @a pos.
1534 * len can be -1 to replace the remainder of @a str.
1535 * returns: pos + strlen(replace). */
1536 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1538 g_string_erase(str, pos, len);
1539 if (replace)
1541 g_string_insert(str, pos, replace);
1542 pos += strlen(replace);
1544 return pos;
1549 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1550 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1551 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1553 * @param haystack The input string to operate on. This string is modified in place.
1554 * @param needle The string which should be replaced.
1555 * @param replace The replacement for @a needle.
1557 * @return Number of replacements made.
1559 GEANY_API_SYMBOL
1560 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1562 guint count = 0;
1563 gint pos = 0;
1564 gsize needle_length = strlen(needle);
1566 while (1)
1568 pos = utils_string_find(haystack, pos, -1, needle);
1570 if (pos == -1)
1571 break;
1573 pos = utils_string_replace(haystack, pos, needle_length, replace);
1574 count++;
1576 return count;
1581 * Replaces only the first occurrence of @a needle in @a haystack
1582 * with @a replace.
1583 * For details, see utils_string_replace_all().
1585 * @param haystack The input string to operate on. This string is modified in place.
1586 * @param needle The string which should be replaced.
1587 * @param replace The replacement for @a needle.
1589 * @return Number of replacements made.
1591 * @since 0.16
1593 GEANY_API_SYMBOL
1594 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1596 gint pos = utils_string_find(haystack, 0, -1, needle);
1598 if (pos == -1)
1599 return 0;
1601 utils_string_replace(haystack, pos, strlen(needle), replace);
1602 return 1;
1606 /* Similar to g_regex_replace but allows matching a subgroup.
1607 * match_num: which match to replace, 0 for whole match.
1608 * literal: FALSE to interpret escape sequences in @a replace.
1609 * returns: number of replacements.
1610 * bug: replaced text can affect matching of ^ or \b */
1611 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1612 guint match_num, const gchar *replace, gboolean literal)
1614 GMatchInfo *minfo;
1615 guint ret = 0;
1616 gint start = 0;
1618 g_assert(literal); /* escapes not implemented yet */
1619 g_return_val_if_fail(replace, 0);
1621 /* ensure haystack->str is not null */
1622 if (haystack->len == 0)
1623 return 0;
1625 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1626 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1628 gint end, len;
1630 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1631 len = end - start;
1632 utils_string_replace(haystack, start, len, replace);
1633 ret++;
1635 /* skip past whole match */
1636 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1637 start = end - len + strlen(replace);
1638 g_match_info_free(minfo);
1640 g_match_info_free(minfo);
1641 return ret;
1645 /* Get project or default startup directory (if set), or NULL. */
1646 const gchar *utils_get_default_dir_utf8(void)
1648 if (app->project && !EMPTY(app->project->base_path))
1650 return app->project->base_path;
1653 if (!EMPTY(prefs.default_open_path))
1655 return prefs.default_open_path;
1657 return NULL;
1662 * Wraps @c spawn_sync(), which see.
1664 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1665 * @param argv The child's argument vector.
1666 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1667 * @param flags Ignored.
1668 * @param child_setup @girskip Ignored.
1669 * @param user_data @girskip Ignored.
1670 * @param std_out @out @optional The return location for child output, or @c NULL.
1671 * @param std_err @out @optional The return location for child error messages, or @c NULL.
1672 * @param exit_status @out @optional The child exit status, as returned by waitpid(), or @c NULL.
1673 * @param error The return location for error or @c NULL.
1675 * @return @c TRUE on success, @c FALSE if an error was set.
1677 GEANY_API_SYMBOL
1678 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1679 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1680 gchar **std_err, gint *exit_status, GError **error)
1682 GString *output = std_out ? g_string_new(NULL) : NULL;
1683 GString *errors = std_err ? g_string_new(NULL) : NULL;
1684 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1686 if (std_out)
1687 *std_out = g_string_free(output, !result);
1689 if (std_err)
1690 *std_err = g_string_free(errors, !result);
1692 return result;
1697 * Wraps @c spawn_async(), which see.
1699 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1700 * @param argv The child's argument vector.
1701 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1702 * @param flags Ignored.
1703 * @param child_setup @girskip Ignored.
1704 * @param user_data Ignored.
1705 * @param child_pid @out @nullable The return location for child process ID, or @c NULL.
1706 * @param error The return location for error or @c NULL.
1708 * @return @c TRUE on success, @c FALSE if an error was set.
1710 GEANY_API_SYMBOL
1711 gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1712 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1713 GError **error)
1715 return spawn_async(dir, NULL, argv, env, child_pid, error);
1719 /* Returns "file:///" on Windows, "file://" everywhere else */
1720 const gchar *utils_get_uri_file_prefix(void)
1722 #ifdef G_OS_WIN32
1723 return "file:///";
1724 #else
1725 return "file://";
1726 #endif
1730 /* Retrieves the path for the given URI.
1731 * It returns:
1732 * - the path which was determined by g_filename_from_uri() or GIO
1733 * - NULL if the URI is non-local and gvfs-fuse is not installed
1734 * - a new copy of 'uri' if it is not an URI. */
1735 gchar *utils_get_path_from_uri(const gchar *uri)
1737 gchar *locale_filename;
1739 g_return_val_if_fail(uri != NULL, NULL);
1741 if (! utils_is_uri(uri))
1742 return g_strdup(uri);
1744 /* this will work only for 'file://' URIs */
1745 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1746 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1747 if (locale_filename == NULL)
1749 GFile *file = g_file_new_for_uri(uri);
1750 locale_filename = g_file_get_path(file);
1751 g_object_unref(file);
1752 if (locale_filename == NULL)
1754 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1755 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1759 return locale_filename;
1763 gboolean utils_is_uri(const gchar *uri)
1765 g_return_val_if_fail(uri != NULL, FALSE);
1767 return (strstr(uri, "://") != NULL);
1771 /* path should be in locale encoding */
1772 gboolean utils_is_remote_path(const gchar *path)
1774 g_return_val_if_fail(path != NULL, FALSE);
1776 /* if path is an URI and it doesn't start "file://", we take it as remote */
1777 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1778 return TRUE;
1780 #ifndef G_OS_WIN32
1782 static gchar *fuse_path = NULL;
1783 static gsize len = 0;
1785 if (G_UNLIKELY(fuse_path == NULL))
1787 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1788 len = strlen(fuse_path);
1790 /* Comparing the file path against a hardcoded path is not the most elegant solution
1791 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1792 * proper GFile objects for Fuse paths, but it only does in future GVFS
1793 * versions (gvfs 1.1.1). */
1794 return (strncmp(path, fuse_path, len) == 0);
1796 #endif
1798 return FALSE;
1802 /* Remove all relative and untidy elements from the path of @a filename.
1803 * @param filename must be a valid absolute path.
1804 * @see utils_get_real_path() - also resolves links. */
1805 void utils_tidy_path(gchar *filename)
1807 GString *str;
1808 const gchar *needle;
1809 gboolean preserve_double_backslash = FALSE;
1811 g_return_if_fail(g_path_is_absolute(filename));
1813 str = g_string_new(filename);
1815 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1816 preserve_double_backslash = TRUE;
1818 #ifdef G_OS_WIN32
1819 /* using MSYS we can get Unix-style separators */
1820 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1821 #endif
1822 /* replace "/./" and "//" */
1823 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1824 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1826 if (preserve_double_backslash)
1827 g_string_prepend(str, "\\");
1829 /* replace "/../" */
1830 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1831 while (1)
1833 const gchar *c = strstr(str->str, needle);
1834 if (c == NULL)
1835 break;
1836 else
1838 gssize pos, sub_len;
1840 pos = c - str->str;
1841 if (pos <= 3)
1842 break; /* bad path */
1844 /* replace "/../" */
1845 g_string_erase(str, pos, strlen(needle));
1846 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1848 /* search for last "/" before found "/../" */
1849 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1850 sub_len = pos - (c - str->str);
1851 if (! c)
1852 break; /* bad path */
1854 pos = c - str->str; /* position of previous "/" */
1855 g_string_erase(str, pos, sub_len);
1858 if (str->len <= strlen(filename))
1859 memcpy(filename, str->str, str->len + 1);
1860 else
1861 g_warn_if_reached();
1862 g_string_free(str, TRUE);
1867 * Removes characters from a string, in place.
1869 * @param string String to search.
1870 * @param chars Characters to remove.
1872 * @return @a string - return value is only useful when nesting function calls, e.g.:
1873 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1875 * @see @c g_strdelimit.
1877 GEANY_API_SYMBOL
1878 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1880 const gchar *r;
1881 gchar *w = string;
1883 g_return_val_if_fail(string, NULL);
1884 if (G_UNLIKELY(EMPTY(chars)))
1885 return string;
1887 foreach_str(r, string)
1889 if (!strchr(chars, *r))
1890 *w++ = *r;
1892 *w = 0x0;
1893 return string;
1897 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1898 GSList *utils_get_config_files(const gchar *subdir)
1900 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1901 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1902 GSList *syslist, *node;
1904 if (!list)
1906 utils_mkdir(path, FALSE);
1908 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1909 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1910 /* merge lists */
1911 list = g_slist_concat(list, syslist);
1913 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1914 /* remove duplicates (next to each other after sorting) */
1915 foreach_slist(node, list)
1917 if (node->next && utils_str_equal(node->next->data, node->data))
1919 GSList *old = node->next;
1921 g_free(old->data);
1922 node->next = old->next;
1923 g_slist_free1(old);
1926 g_free(path);
1927 return list;
1931 /* Suffix can be NULL or a string which should be appended to the Help URL like
1932 * an anchor link, e.g. "#some_anchor". */
1933 gchar *utils_get_help_url(const gchar *suffix)
1935 gchar *uri;
1936 const gchar *uri_file_prefix = utils_get_uri_file_prefix();
1937 gint skip = strlen(uri_file_prefix);
1939 uri = g_strconcat(uri_file_prefix, app->docdir, "/index.html", NULL);
1940 #ifdef G_OS_WIN32
1941 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1942 #endif
1944 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1945 { /* fall back to online documentation if it is not found on the hard disk */
1946 g_free(uri);
1947 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", VERSION, "/index.html", NULL);
1950 if (suffix != NULL)
1952 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1955 return uri;
1959 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1961 const gchar **p;
1963 for (p = haystack; *p != NULL; ++p)
1965 if (utils_str_equal(*p, needle))
1966 return TRUE;
1968 return FALSE;
1973 * Copies the current environment into a new array.
1974 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1975 * All further arguments are key, value pairs of variables which should be added to
1976 * the environment.
1978 * The argument list must be @c NULL-terminated.
1980 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1981 * @param first_varname Name of the first variable to copy into the new array.
1982 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1984 * @return @transfer{full} The new environment array. Use @c g_strfreev() to free it.
1986 GEANY_API_SYMBOL
1987 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1989 gchar **result;
1990 gchar **p;
1991 gchar **env;
1992 va_list args;
1993 const gchar *key, *value;
1994 guint n, o;
1996 /* count the additional variables */
1997 va_start(args, first_varname);
1998 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1999 va_end(args);
2000 /* the passed arguments should be even (key, value pairs) */
2001 g_return_val_if_fail(o % 2 == 0, NULL);
2003 o /= 2;
2005 /* get all the environ variables */
2006 env = g_listenv();
2008 /* create an array large enough to hold the new environment */
2009 n = g_strv_length(env);
2010 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
2011 result = g_new(gchar *, n + o + 1);
2013 /* copy the environment */
2014 for (n = 0, p = env; *p != NULL; ++p)
2016 /* copy the variable */
2017 value = g_getenv(*p);
2018 if (G_LIKELY(value != NULL))
2020 /* skip excluded variables */
2021 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
2022 continue;
2024 result[n++] = g_strconcat(*p, "=", value, NULL);
2027 g_strfreev(env);
2029 /* now add additional variables */
2030 va_start(args, first_varname);
2031 key = first_varname;
2032 value = va_arg(args, gchar*);
2033 while (key != NULL)
2035 result[n++] = g_strconcat(key, "=", value, NULL);
2037 key = va_arg(args, gchar*);
2038 if (key == NULL)
2039 break;
2040 value = va_arg(args, gchar*);
2042 va_end(args);
2044 result[n] = NULL;
2046 return result;
2050 /* Joins @a first and @a second into a new string vector, freeing the originals.
2051 * The original contents are reused. */
2052 gchar **utils_strv_join(gchar **first, gchar **second)
2054 gchar **strv;
2055 gchar **rptr, **wptr;
2057 if (!first)
2058 return second;
2059 if (!second)
2060 return first;
2062 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2063 wptr = strv;
2065 foreach_strv(rptr, first)
2066 *wptr++ = *rptr;
2067 foreach_strv(rptr, second)
2068 *wptr++ = *rptr;
2070 g_free(first);
2071 g_free(second);
2072 return strv;
2075 /* * Returns the common prefix in a list of strings.
2077 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2079 * @param strv The list of strings to process.
2080 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2082 * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list
2083 * was passed in.
2085 GEANY_EXPORT_SYMBOL
2086 gchar *utils_strv_find_common_prefix(gchar **strv, gssize strv_len)
2088 gsize num;
2090 if (strv_len == 0)
2091 return NULL;
2093 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2095 for (gsize i = 0; strv[0][i]; i++)
2097 for (gsize j = 1; j < num; j++)
2099 if (strv[j][i] != strv[0][i])
2101 /* return prefix on first mismatch */
2102 return g_strndup(strv[0], i);
2107 return g_strdup(strv[0]);
2111 /* * Returns the longest common substring in a list of strings.
2113 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2115 * @param strv The list of strings to process.
2116 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2118 * @return The common prefix that is part of all strings.
2120 GEANY_EXPORT_SYMBOL
2121 gchar *utils_strv_find_lcs(gchar **strv, gssize strv_len, const gchar *delim)
2123 gchar *first, *_sub, *sub;
2124 gsize num;
2125 gsize n_chars;
2126 gsize len;
2127 gsize max = 0;
2128 char *lcs;
2129 gsize found;
2131 if (strv_len == 0)
2132 return NULL;
2134 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2136 first = strv[0];
2137 len = strlen(first);
2139 /* sub is the working area where substrings from first are copied to */
2140 sub = g_malloc(len+1);
2141 lcs = g_strdup("");
2142 foreach_str(_sub, first)
2144 gsize chars_left = len - (_sub - first);
2145 /* No point in continuing if the remainder is too short */
2146 if (max > chars_left)
2147 break;
2148 /* If delimiters are given, we only need to compare substrings which start and
2149 * end with one of them, so skip any non-delim chars at front ... */
2150 if (NZV(delim) && (strchr(delim, _sub[0]) == NULL))
2151 continue;
2152 for (n_chars = 1; n_chars <= chars_left; n_chars++)
2154 if (NZV(delim))
2155 { /* ... and advance to the next delim char at the end, if any */
2156 if (!_sub[n_chars] || strchr(delim, _sub[n_chars]) == NULL)
2157 continue;
2158 n_chars += 1;
2160 g_strlcpy(sub, _sub, n_chars+1);
2161 found = 1;
2162 for (gsize i = 1; i < num; i++)
2164 if (strstr(strv[i], sub) == NULL)
2165 break;
2166 found++;
2168 if (found == num && n_chars > max)
2170 max = n_chars;
2171 SETPTR(lcs, g_strdup(sub));
2175 g_free(sub);
2177 return lcs;
2181 /** Transform file names in a list to be shorter.
2183 * This function takes a list of file names (probably with absolute paths), and
2184 * transforms the paths such that they are short but still unique. This is intended
2185 * for dialogs which present the file list to the user, where the base name may result
2186 * in duplicates (showing the full path might be inappropriate).
2188 * The algorthm strips the common prefix (e-g. the user's home directory) and
2189 * replaces the longest common substring with an ellipsis ("...").
2191 * @param file_names @array{length=file_names_len} The list of strings to process.
2192 * @param file_names_len The number of strings contained in @a file_names. Can be -1 if it's
2193 * terminated by @c NULL.
2194 * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by
2195 @c NULL. Use @c g_strfreev() to free it.
2197 * @since 1.34 (API 239)
2199 GEANY_API_SYMBOL
2200 gchar **utils_strv_shorten_file_list(gchar **file_names, gssize file_names_len)
2202 gsize num;
2203 gsize i;
2204 gchar *prefix, *lcs, *end;
2205 gchar **names;
2206 gsize prefix_len = 0, lcs_len = 0;
2208 if (file_names_len == 0)
2209 return g_new0(gchar *, 1);
2211 g_return_val_if_fail(file_names != NULL, NULL);
2213 num = (file_names_len == -1) ? g_strv_length(file_names) : (gsize) file_names_len;
2214 /* Always include a terminating NULL, enables easy freeing with g_strfreev()
2215 * We just copy the pointers so we can advance them here. But don't
2216 * forget to duplicate the strings before returning.
2218 names = g_new(gchar *, num + 1);
2219 memcpy(names, file_names, num * sizeof(gchar *));
2220 /* Always include a terminating NULL, enables easy freeing with g_strfreev() */
2221 names[num] = NULL;
2223 /* First: determine the common prefix, that will be stripped.
2224 * We only want to strip full path components, including the trailing slash.
2225 * Except if the component is just "/".
2227 prefix = utils_strv_find_common_prefix(names, num);
2228 end = strrchr(prefix, G_DIR_SEPARATOR);
2229 if (end && end > prefix)
2231 prefix_len = end - prefix + 1; /* prefix_len includes the trailing slash */
2232 for (i = 0; i < num; i++)
2233 names[i] += prefix_len;
2236 /* Second: determine the longest common substring (lcs), that will be ellipsized. Again,
2237 * we look only for full path compnents so that we ellipsize between separators. This implies
2238 * that the file name cannot be ellipsized which is desirable anyway.
2240 lcs = utils_strv_find_lcs(names, num, G_DIR_SEPARATOR_S"/");
2241 if (lcs)
2243 lcs_len = strlen(lcs);
2244 /* Don't bother for tiny common parts (which are often just "." or "/"). Beware
2245 * that lcs includes the enclosing dir separators so the part must be at least 5 chars
2246 * to be eligible for ellipsizing.
2248 if (lcs_len < 7)
2249 lcs_len = 0;
2252 /* Last: build the shortened list of unique file names */
2253 for (i = 0; i < num; i++)
2255 if (lcs_len == 0)
2256 { /* no lcs, copy without prefix */
2257 names[i] = g_strdup(names[i]);
2259 else
2261 const gchar *lcs_start = strstr(names[i], lcs);
2262 const gchar *lcs_end = lcs_start + lcs_len;
2263 /* Dir seperators are included in lcs but shouldn't be elipsized. */
2264 names[i] = g_strdup_printf("%.*s...%s", (int)(lcs_start - names[i] + 1), names[i], lcs_end - 1);
2268 g_free(lcs);
2269 g_free(prefix);
2271 return names;
2275 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2276 * obviously g_date_set_parse() uses some magic.
2277 * The returned GDate object must be freed. */
2278 GDate *utils_parse_date(const gchar *input)
2280 GDate *date = g_date_new();
2282 g_date_set_parse(date, input);
2284 if (g_date_valid(date))
2285 return date;
2287 g_date_free(date);
2288 return NULL;
2292 gchar *utils_parse_and_format_build_date(const gchar *input)
2294 gchar date_buf[255];
2295 GDate *date = utils_parse_date(input);
2297 if (date != NULL)
2299 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2300 g_date_free(date);
2301 return g_strdup(date_buf);
2304 return g_strdup(input);
2308 gchar *utils_get_user_config_dir(void)
2310 #ifdef G_OS_WIN32
2311 return win32_get_user_config_dir();
2312 #else
2313 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2314 #endif
2318 static gboolean is_osx_bundle(void)
2320 #ifdef MAC_INTEGRATION
2321 gchar *bundle_id = gtkosx_application_get_bundle_id();
2322 if (bundle_id)
2324 g_free(bundle_id);
2325 return TRUE;
2327 #endif
2328 return FALSE;
2332 const gchar *utils_resource_dir(GeanyResourceDirType type)
2334 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2336 if (!resdirs[RESOURCE_DIR_DATA])
2338 #ifdef G_OS_WIN32
2339 gchar *prefix = win32_get_installation_dir();
2341 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", NULL);
2342 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2343 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2344 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2345 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2346 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2347 g_free(prefix);
2348 #else
2349 if (is_osx_bundle())
2351 # ifdef MAC_INTEGRATION
2352 gchar *prefix = gtkosx_application_get_resource_path();
2354 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2355 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2356 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2357 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2358 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2359 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2360 g_free(prefix);
2361 # endif
2363 else
2365 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2366 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2367 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2368 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2369 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2370 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(GEANY_LIBEXECDIR, "geany", NULL);
2372 #endif
2375 return resdirs[type];
2379 void utils_start_new_geany_instance(const gchar *doc_path)
2381 const gchar *command = is_osx_bundle() ? "open" : "geany";
2382 gchar *exec_path = g_find_program_in_path(command);
2384 if (exec_path)
2386 GError *err = NULL;
2387 const gchar *argv[6]; // max args + 1
2388 gint argc = 0;
2390 argv[argc++] = exec_path;
2391 if (is_osx_bundle())
2393 argv[argc++] = "-n";
2394 argv[argc++] = "-a";
2395 argv[argc++] = "Geany";
2396 argv[argc++] = doc_path;
2398 else
2400 argv[argc++] = "-i";
2401 argv[argc++] = doc_path;
2403 argv[argc] = NULL;
2405 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2407 g_printerr("Unable to open new window: %s\n", err->message);
2408 g_error_free(err);
2410 g_free(exec_path);
2412 else
2413 g_printerr("Unable to find 'geany'\n");
2418 * Get a link-dereferenced, absolute version of a file name.
2420 * This is similar to the POSIX `realpath` function when passed a
2421 * @c NULL argument.
2423 * @warning This function suffers the same problems as the POSIX
2424 * function `realpath()`, namely that it's impossible to determine
2425 * a suitable size for the returned buffer, and so it's limited to a
2426 * maximum of `PATH_MAX`.
2428 * @param file_name The file name to get the real path of.
2430 * @return A newly-allocated string containing the real path which
2431 * should be freed with `g_free()` when no longer needed, or @c NULL
2432 * if the real path cannot be obtained.
2434 * @since 1.32 (API 235)
2436 GEANY_API_SYMBOL
2437 gchar *utils_get_real_path(const gchar *file_name)
2439 return tm_get_real_path(file_name);
2444 * Get a string describing the OS.
2446 * If the OS can be determined, a string which describes the OS will
2447 * be returned. If no OS can be determined then `NULL` will be returned.
2449 * @note The format of the returned string is unspecified and is only
2450 * meant to provide diagnostic information to the user.
2452 * @return A newly-allocated string containing a description of the
2453 * OS if it can be determined or `NULL` if it cannot.
2455 * @since 1.37
2457 gchar *utils_get_os_info_string(void)
2459 gchar *os_info = NULL;
2461 #if GLIB_CHECK_VERSION(2, 64, 0)
2462 # if ! defined(__APPLE__)
2463 /* on non-macOS operating systems */
2465 GString *os_str;
2466 gchar *pretty_name;
2467 gchar *code_name;
2469 pretty_name = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
2470 if (pretty_name == NULL)
2471 return NULL;
2473 os_str = g_string_new(pretty_name);
2474 g_free(pretty_name);
2476 code_name = g_get_os_info(G_OS_INFO_KEY_VERSION_CODENAME);
2477 if (code_name != NULL)
2479 g_string_append_printf(os_str, " (%s)", code_name);
2480 g_free(code_name);
2483 os_info = g_string_free(os_str, FALSE);
2485 # else
2486 /* on macOS, only `G_OS_INFO_KEY_NAME` is supported and returns the
2487 * fixed string `macOS` */
2488 os_info = g_get_os_info(G_OS_INFO_KEY_NAME);
2489 # endif
2490 #else
2491 /* if g_get_os_info() is not available, do it the old-fashioned way */
2492 # if defined(_WIN64)
2493 os_info = g_strdup("Microsoft Windows (64-bit)");
2494 # elif defined(_WIN32)
2495 os_info = g_strdup("Microsoft Windows");
2496 # elif defined(__APPLE__)
2497 os_info = g_strdup("Apple macOS");
2498 # elif defined(__linux__)
2499 os_info = g_strdup("Linux");
2500 # elif defined(__FreeBSD__)
2501 os_info = g_strdup("FreeBSD");
2502 # elif defined(__ANDROID__)
2503 os_info = g_strdup("Android");
2504 # endif
2505 #endif
2507 return os_info;