Improve tag-goto popup
[geany-mirror.git] / src / utils.c
blob2a8cccddb5fe8f95571bbb4f54be79b55ab7d9b2
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 gchar *utils_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 = utils_utf8_strdown(s1);
516 if (! tmp1)
517 return 1;
518 tmp2 = utils_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 GDateTime.
724 * This function works on UTF-8 encoded strings.
726 * @param format The format string to pass to g_date_time_format, in UTF-8 encoding.
727 See https://docs.gtk.org/glib/method.DateTime.format.html for details.
728 * @param time_to_use @nullable The date/time to use, in time_t format or @c NULL to use the current time.
730 * @return A newly-allocated string, should be freed when no longer needed.
732 * @since 0.16
734 GEANY_API_SYMBOL
735 gchar *utils_get_date_time(const gchar *format, time_t *time_to_use)
737 time_t unixtime;
738 gchar *datetime_formatted;
739 GDateTime *datetime;
741 g_return_val_if_fail(format != NULL, NULL);
743 if (time_to_use != NULL)
744 unixtime = *time_to_use;
745 else
746 unixtime = time(NULL);
748 datetime = g_date_time_new_from_unix_local(unixtime);
749 datetime_formatted = g_date_time_format(datetime, format);
751 g_date_time_unref(datetime);
752 return datetime_formatted;
756 gchar *utils_get_initials(const gchar *name)
758 gint i = 1, j = 1;
759 gchar *initials = g_malloc0(5);
761 initials[0] = name[0];
762 while (name[i] != '\0' && j < 4)
764 if (name[i] == ' ' && name[i + 1] != ' ')
766 initials[j++] = name[i + 1];
768 i++;
770 return initials;
775 * Wraps g_key_file_get_integer() to add a default value argument.
777 * @param config A GKeyFile object.
778 * @param section The group name to look in for the key.
779 * @param key The key to find.
780 * @param default_value The default value which will be returned when @a section or @a key
781 * don't exist.
783 * @return The value associated with @a key as an integer, or the given default value if the value
784 * could not be retrieved.
786 GEANY_API_SYMBOL
787 gint utils_get_setting_integer(GKeyFile *config, const gchar *section, const gchar *key,
788 const gint default_value)
790 gint tmp;
791 GError *error = NULL;
793 g_return_val_if_fail(config, default_value);
795 tmp = g_key_file_get_integer(config, section, key, &error);
796 if (error)
798 g_error_free(error);
799 return default_value;
801 return tmp;
806 * Wraps g_key_file_get_boolean() to add a default value argument.
808 * @param config A GKeyFile object.
809 * @param section The group name to look in for the key.
810 * @param key The key to find.
811 * @param default_value The default value which will be returned when @c section or @c key
812 * don't exist.
814 * @return The value associated with @a key as a boolean, or the given default value if the value
815 * could not be retrieved.
817 GEANY_API_SYMBOL
818 gboolean utils_get_setting_boolean(GKeyFile *config, const gchar *section, const gchar *key,
819 const gboolean default_value)
821 gboolean tmp;
822 GError *error = NULL;
824 g_return_val_if_fail(config, default_value);
826 tmp = g_key_file_get_boolean(config, section, key, &error);
827 if (error)
829 g_error_free(error);
830 return default_value;
832 return tmp;
837 * Wraps g_key_file_get_double() to add a default value argument.
839 * @param config A GKeyFile object.
840 * @param section The group name to look in for the key.
841 * @param key The key to find.
842 * @param default_value The default value which will be returned when @a section or @a key
843 * don't exist.
845 * @return The value associated with @a key as an integer, or the given default value if the value
846 * could not be retrieved.
848 GEANY_API_SYMBOL
849 gdouble utils_get_setting_double(GKeyFile *config, const gchar *section, const gchar *key,
850 const gdouble default_value)
852 gdouble tmp;
853 GError *error = NULL;
855 g_return_val_if_fail(config, default_value);
857 tmp = g_key_file_get_double(config, section, key, &error);
858 if (error)
860 g_error_free(error);
861 return default_value;
863 return tmp;
868 * Wraps g_key_file_get_string() to add a default value argument.
870 * @param config A GKeyFile object.
871 * @param section The group name to look in for the key.
872 * @param key The key to find.
873 * @param default_value The default value which will be returned when @a section or @a key
874 * don't exist.
876 * @return A newly allocated string, either the value for @a key or a copy of the given
877 * default value if it could not be retrieved.
879 GEANY_API_SYMBOL
880 gchar *utils_get_setting_string(GKeyFile *config, const gchar *section, const gchar *key,
881 const gchar *default_value)
883 gchar *tmp;
885 g_return_val_if_fail(config, g_strdup(default_value));
887 tmp = g_key_file_get_string(config, section, key, NULL);
888 if (!tmp)
890 return g_strdup(default_value);
892 return tmp;
896 gchar *utils_get_hex_from_color(GdkColor *color)
898 g_return_val_if_fail(color != NULL, NULL);
900 return g_strdup_printf("#%02X%02X%02X",
901 (guint) (utils_scale_round(color->red / 65535.0, 255)),
902 (guint) (utils_scale_round(color->green / 65535.0, 255)),
903 (guint) (utils_scale_round(color->blue / 65535.0, 255)));
907 /* Get directory from current file in the notebook.
908 * Returns dir string that should be freed or NULL, depending on whether current file is valid.
909 * Returned string is in UTF-8 encoding */
910 gchar *utils_get_current_file_dir_utf8(void)
912 GeanyDocument *doc = document_get_current();
914 if (doc != NULL)
916 /* get current filename */
917 const gchar *cur_fname = doc->file_name;
919 if (cur_fname != NULL)
921 /* get folder part from current filename */
922 return g_path_get_dirname(cur_fname); /* returns "." if no path */
926 return NULL; /* no file open */
930 /* very simple convenience function */
931 void utils_beep(void)
933 if (prefs.beep_on_errors)
934 gdk_beep();
938 /* taken from busybox, thanks */
939 gchar *utils_make_human_readable_str(guint64 size, gulong block_size,
940 gulong display_unit)
942 /* The code will adjust for additional (appended) units. */
943 static const gchar zero_and_units[] = { '0', 0, 'K', 'M', 'G', 'T' };
944 static const gchar fmt[] = "%Lu %c%c";
945 static const gchar fmt_tenths[] = "%Lu.%d %c%c";
947 guint64 val;
948 gint frac;
949 const gchar *u;
950 const gchar *f;
952 u = zero_and_units;
953 f = fmt;
954 frac = 0;
956 val = size * block_size;
957 if (val == 0)
958 return g_strdup(u);
960 if (display_unit)
962 val += display_unit/2; /* Deal with rounding. */
963 val /= display_unit; /* Don't combine with the line above!!! */
965 else
967 ++u;
968 while ((val >= 1024) && (u < zero_and_units + sizeof(zero_and_units) - 1))
970 f = fmt_tenths;
971 ++u;
972 frac = ((((gint)(val % 1024)) * 10) + (1024 / 2)) / 1024;
973 val /= 1024;
975 if (frac >= 10)
976 { /* We need to round up here. */
977 ++val;
978 frac = 0;
982 /* If f==fmt then 'frac' and 'u' are ignored. */
983 return g_strdup_printf(f, val, frac, *u, 'b');
987 /* converts a color representation using gdk_color_parse(), with additional
988 * support of the "0x" prefix as a synonym for "#" */
989 gboolean utils_parse_color(const gchar *spec, GdkColor *color)
991 gchar buf[64] = {0};
993 g_return_val_if_fail(spec != NULL, -1);
995 if (spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X'))
997 /* convert to # format for GDK to understand it */
998 buf[0] = '#';
999 strncpy(buf + 1, spec + 2, sizeof(buf) - 2);
1000 spec = buf;
1003 return gdk_color_parse(spec, color);
1007 /* converts a GdkColor to the packed 24 bits BGR format, as understood by Scintilla
1008 * returns a 24 bits BGR color, or -1 on failure */
1009 gint utils_color_to_bgr(const GdkColor *c)
1011 g_return_val_if_fail(c != NULL, -1);
1012 return (c->red / 256) | ((c->green / 256) << 8) | ((c->blue / 256) << 16);
1016 /* parses @p spec using utils_parse_color() and convert it to 24 bits BGR using
1017 * utils_color_to_bgr() */
1018 gint utils_parse_color_to_bgr(const gchar *spec)
1020 GdkColor color;
1021 if (utils_parse_color(spec, &color))
1022 return utils_color_to_bgr(&color);
1023 else
1024 return -1;
1028 /* Returns: newly allocated string with the current time formatted HH:MM:SS.
1029 * If "include_microseconds" is TRUE, microseconds are appended.
1031 * The returned string should be freed with g_free(). */
1032 gchar *utils_get_current_time_string(gboolean include_microseconds)
1034 // "%f" specifier for microseconds is only available since GLib 2.66
1035 if (glib_check_version(2, 66, 0) != NULL)
1036 include_microseconds = FALSE;
1038 GDateTime *now = g_date_time_new_now_local();
1039 const gchar *format = include_microseconds ? "%H:%M:%S.%f" : "%H:%M:%S";
1040 gchar *time_string = g_date_time_format(now, format);
1041 g_date_time_unref(now);
1042 return time_string;
1046 GIOChannel *utils_set_up_io_channel(
1047 gint fd, GIOCondition cond, gboolean nblock, GIOFunc func, gpointer data)
1049 GIOChannel *ioc;
1050 /*const gchar *encoding;*/
1052 #ifdef G_OS_WIN32
1053 ioc = g_io_channel_win32_new_fd(fd);
1054 #else
1055 ioc = g_io_channel_unix_new(fd);
1056 #endif
1058 if (nblock)
1059 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
1061 g_io_channel_set_encoding(ioc, NULL, NULL);
1063 if (! g_get_charset(&encoding))
1064 { // hope this works reliably
1065 GError *error = NULL;
1066 g_io_channel_set_encoding(ioc, encoding, &error);
1067 if (error)
1069 geany_debug("%s: %s", G_STRFUNC, error->message);
1070 g_error_free(error);
1071 return ioc;
1075 /* "auto-close" ;-) */
1076 g_io_channel_set_close_on_unref(ioc, TRUE);
1078 g_io_add_watch(ioc, cond, func, data);
1079 g_io_channel_unref(ioc);
1081 return ioc;
1085 /* Contributed by Stefan Oltmanns, thanks.
1086 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1087 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1088 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1090 gsize i, j, len;
1091 guint unicodechar;
1093 g_return_val_if_fail(string != NULL, FALSE);
1095 j = 0;
1096 len = strlen(string);
1097 for (i = 0; i < len; i++)
1099 if (string[i]=='\\')
1101 if (i++ >= strlen(string))
1103 return FALSE;
1105 switch (string[i])
1107 case '\\':
1108 if (keep_backslash)
1109 string[j++] = '\\';
1110 string[j] = '\\';
1111 break;
1112 case 'n':
1113 string[j] = '\n';
1114 break;
1115 case 'r':
1116 string[j] = '\r';
1117 break;
1118 case 't':
1119 string[j] = '\t';
1120 break;
1121 #if 0
1122 case 'x': /* Warning: May produce illegal utf-8 string! */
1123 i += 2;
1124 if (i >= strlen(string))
1126 return FALSE;
1128 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1129 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1130 else return FALSE;
1131 string[j] <<= 4;
1132 if (isdigit(string[i])) string[j] |= string[i] - 48;
1133 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1134 else return FALSE;
1135 break;
1136 #endif
1137 case 'u':
1139 i += 2;
1140 if (i >= strlen(string))
1142 return FALSE;
1144 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1145 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1146 else return FALSE;
1147 unicodechar <<= 4;
1148 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1149 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1150 else return FALSE;
1151 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1152 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1154 i += 2;
1155 unicodechar <<= 8;
1156 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1157 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1158 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1159 else unicodechar |= tolower(string[i])-87;
1161 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1162 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1164 i += 2;
1165 unicodechar <<= 8;
1166 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1167 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1168 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1169 else unicodechar |= tolower(string[i])-87;
1171 if (unicodechar < 0x80)
1173 string[j] = unicodechar;
1175 else if (unicodechar < 0x800)
1177 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1178 j++;
1179 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1181 else if (unicodechar < 0x10000)
1183 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1184 j++;
1185 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1186 j++;
1187 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1189 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1191 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1192 j++;
1193 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1194 j++;
1195 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1196 j++;
1197 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1199 else
1201 return FALSE;
1203 break;
1205 default:
1206 /* unnecessary escapes are allowed */
1207 if (keep_backslash)
1208 string[j++] = '\\';
1209 string[j] = string[i];
1212 else
1214 string[j] = string[i];
1216 j++;
1218 while (j < i)
1220 string[j] = 0;
1221 j++;
1223 return TRUE;
1227 /* Wraps a string in place, replacing a space with a newline character.
1228 * wrapstart is the minimum position to start wrapping or -1 for default */
1229 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1231 gchar *pos, *linestart;
1232 gboolean ret = FALSE;
1234 if (wrapstart < 0)
1235 wrapstart = 80;
1237 for (pos = linestart = string; *pos != '\0'; pos++)
1239 if (pos - linestart >= wrapstart && *pos == ' ')
1241 *pos = '\n';
1242 linestart = pos;
1243 ret = TRUE;
1246 return ret;
1251 * Converts the given UTF-8 encoded string into locale encoding.
1252 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1254 * @param utf8_text UTF-8 encoded text.
1256 * @return The converted string in locale encoding, or a copy of the input string if conversion
1257 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1259 GEANY_API_SYMBOL
1260 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1262 #ifdef G_OS_WIN32
1263 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1264 * which would result in wrongly converted strings */
1265 return g_strdup(utf8_text);
1266 #else
1267 gchar *locale_text;
1269 if (! utf8_text)
1270 return NULL;
1271 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1272 if (locale_text == NULL)
1273 locale_text = g_strdup(utf8_text);
1274 return locale_text;
1275 #endif
1280 * Converts the given string (in locale encoding) into UTF-8 encoding.
1281 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1283 * @param locale_text Text in locale encoding.
1285 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1286 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1288 GEANY_API_SYMBOL
1289 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1291 #ifdef G_OS_WIN32
1292 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1293 * which would result in wrongly converted strings */
1294 return g_strdup(locale_text);
1295 #else
1296 gchar *utf8_text;
1298 if (! locale_text)
1299 return NULL;
1300 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1301 if (utf8_text == NULL)
1302 utf8_text = g_strdup(locale_text);
1303 return utf8_text;
1304 #endif
1308 /* Pass pointers to free after arg_count.
1309 * The last argument must be NULL as an extra check that arg_count is correct. */
1310 void utils_free_pointers(gsize arg_count, ...)
1312 va_list a;
1313 gsize i;
1314 gpointer ptr;
1316 va_start(a, arg_count);
1317 for (i = 0; i < arg_count; i++)
1319 ptr = va_arg(a, gpointer);
1320 g_free(ptr);
1322 ptr = va_arg(a, gpointer);
1323 if (ptr)
1324 g_warning("Wrong arg_count!");
1325 va_end(a);
1329 /* Creates a string array deep copy of a series of non-NULL strings.
1330 * The first argument is nothing special and must not be NULL.
1331 * The list must be terminated with NULL. */
1332 GEANY_EXPORT_SYMBOL
1333 gchar **utils_strv_new(const gchar *first, ...)
1335 gsize strvlen, i;
1336 va_list args;
1337 gchar *str;
1338 gchar **strv;
1340 g_return_val_if_fail(first != NULL, NULL);
1342 strvlen = 1; /* for first argument */
1344 /* count other arguments */
1345 va_start(args, first);
1346 for (; va_arg(args, gchar*) != NULL; strvlen++);
1347 va_end(args);
1349 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1350 strv[0] = g_strdup(first);
1352 va_start(args, first);
1353 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1355 strv[i] = g_strdup(str);
1357 va_end(args);
1359 strv[i] = NULL;
1360 return strv;
1365 * Creates a directory if it doesn't already exist.
1366 * Creates intermediate parent directories as needed, too.
1367 * The permissions of the created directory are set 0700.
1369 * @param path The path of the directory to create, in locale encoding.
1370 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1372 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1373 * failed operation is returned.
1375 GEANY_API_SYMBOL
1376 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1378 gint mode = 0700;
1379 gint result;
1381 if (path == NULL || strlen(path) == 0)
1382 return EFAULT;
1384 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1385 if (result != 0)
1386 return errno;
1387 return 0;
1392 * Gets a list of files from the specified directory.
1393 * Locale encoding is expected for @a path and used for the file list. The list and the data
1394 * in the list should be freed after use, e.g.:
1395 * @code
1396 * g_slist_foreach(list, (GFunc) g_free, NULL);
1397 * g_slist_free(list); @endcode
1399 * @note If you don't need a list you should use the foreach_dir() macro instead -
1400 * it's more efficient.
1402 * @param path The path of the directory to scan, in locale encoding.
1403 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1404 * will use more memory.
1405 * @param sort Whether to sort alphabetically (UTF-8 safe).
1406 * @param error The location for storing a possible error, or @c NULL.
1408 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL if
1409 * no files were found. The list and its data should be freed when no longer needed.
1410 * @see utils_get_file_list().
1412 GEANY_API_SYMBOL
1413 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1415 GSList *list = NULL;
1416 GDir *dir;
1417 const gchar *filename;
1419 if (error)
1420 *error = NULL;
1421 g_return_val_if_fail(path != NULL, NULL);
1423 dir = g_dir_open(path, 0, error);
1424 if (dir == NULL)
1425 return NULL;
1427 foreach_dir(filename, dir)
1429 list = g_slist_prepend(list, full_path ?
1430 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1432 g_dir_close(dir);
1433 /* sorting last is quicker than on insertion */
1434 if (sort)
1435 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1436 return list;
1441 * Gets a sorted list of files from the specified directory.
1442 * Locale encoding is expected for @a path and used for the file list. The list and the data
1443 * in the list should be freed after use, e.g.:
1444 * @code
1445 * g_slist_foreach(list, (GFunc) g_free, NULL);
1446 * g_slist_free(list); @endcode
1448 * @param path The path of the directory to scan, in locale encoding.
1449 * @param length The location to store the number of non-@c NULL data items in the list,
1450 * unless @c NULL.
1451 * @param error The location for storing a possible error, or @c NULL.
1453 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL
1454 * if no files were found. The list and its data should be freed when no longer needed.
1455 * @see utils_get_file_list_full().
1457 GEANY_API_SYMBOL
1458 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1460 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1462 if (length)
1463 *length = g_slist_length(list);
1464 return list;
1468 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1469 gboolean utils_str_has_upper(const gchar *str)
1471 gunichar c;
1473 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1474 return FALSE;
1476 while (*str != '\0')
1478 c = g_utf8_get_char(str);
1479 /* check only letters and stop once the first non-capital was found */
1480 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1481 return TRUE;
1482 /* FIXME don't write a const string */
1483 str = g_utf8_next_char(str);
1485 return FALSE;
1489 /* end can be -1 for haystack->len.
1490 * returns: position of found text or -1. */
1491 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1493 gint pos;
1495 g_return_val_if_fail(haystack != NULL, -1);
1496 if (haystack->len == 0)
1497 return -1;
1499 g_return_val_if_fail(start >= 0, -1);
1500 if (start >= (gint)haystack->len)
1501 return -1;
1503 g_return_val_if_fail(!EMPTY(needle), -1);
1505 if (end < 0)
1506 end = haystack->len;
1508 pos = utils_strpos(haystack->str + start, needle);
1509 if (pos == -1)
1510 return -1;
1512 pos += start;
1513 if (pos >= end)
1514 return -1;
1515 return pos;
1519 /* Replaces @len characters from offset @a pos.
1520 * len can be -1 to replace the remainder of @a str.
1521 * returns: pos + strlen(replace). */
1522 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1524 g_string_erase(str, pos, len);
1525 if (replace)
1527 g_string_insert(str, pos, replace);
1528 pos += strlen(replace);
1530 return pos;
1535 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1536 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1537 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1539 * @param haystack The input string to operate on. This string is modified in place.
1540 * @param needle The string which should be replaced.
1541 * @param replace The replacement for @a needle.
1543 * @return Number of replacements made.
1545 GEANY_API_SYMBOL
1546 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1548 guint count = 0;
1549 gint pos = 0;
1550 gsize needle_length = strlen(needle);
1552 while (1)
1554 pos = utils_string_find(haystack, pos, -1, needle);
1556 if (pos == -1)
1557 break;
1559 pos = utils_string_replace(haystack, pos, needle_length, replace);
1560 count++;
1562 return count;
1567 * Replaces only the first occurrence of @a needle in @a haystack
1568 * with @a replace.
1569 * For details, see utils_string_replace_all().
1571 * @param haystack The input string to operate on. This string is modified in place.
1572 * @param needle The string which should be replaced.
1573 * @param replace The replacement for @a needle.
1575 * @return Number of replacements made.
1577 * @since 0.16
1579 GEANY_API_SYMBOL
1580 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1582 gint pos = utils_string_find(haystack, 0, -1, needle);
1584 if (pos == -1)
1585 return 0;
1587 utils_string_replace(haystack, pos, strlen(needle), replace);
1588 return 1;
1592 /* Similar to g_regex_replace but allows matching a subgroup.
1593 * match_num: which match to replace, 0 for whole match.
1594 * literal: FALSE to interpret escape sequences in @a replace.
1595 * returns: number of replacements.
1596 * bug: replaced text can affect matching of ^ or \b */
1597 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1598 guint match_num, const gchar *replace, gboolean literal)
1600 GMatchInfo *minfo;
1601 guint ret = 0;
1602 gint start = 0;
1604 g_assert(literal); /* escapes not implemented yet */
1605 g_return_val_if_fail(replace, 0);
1607 /* ensure haystack->str is not null */
1608 if (haystack->len == 0)
1609 return 0;
1611 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1612 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1614 gint end, len;
1616 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1617 len = end - start;
1618 utils_string_replace(haystack, start, len, replace);
1619 ret++;
1621 /* skip past whole match */
1622 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1623 start = end - len + strlen(replace);
1624 g_match_info_free(minfo);
1626 g_match_info_free(minfo);
1627 return ret;
1631 /* Get project or default startup directory (if set), or NULL. */
1632 const gchar *utils_get_default_dir_utf8(void)
1634 if (app->project && !EMPTY(app->project->base_path))
1636 return app->project->base_path;
1639 if (!EMPTY(prefs.default_open_path))
1641 return prefs.default_open_path;
1643 return NULL;
1648 * Wraps @c spawn_sync(), which see.
1650 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1651 * @param argv The child's argument vector.
1652 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1653 * @param flags Ignored.
1654 * @param child_setup @girskip Ignored.
1655 * @param user_data @girskip Ignored.
1656 * @param std_out @out @optional The return location for child output, or @c NULL.
1657 * @param std_err @out @optional The return location for child error messages, or @c NULL.
1658 * @param exit_status @out @optional The child exit status, as returned by waitpid(), or @c NULL.
1659 * @param error The return location for error or @c NULL.
1661 * @return @c TRUE on success, @c FALSE if an error was set.
1663 GEANY_API_SYMBOL
1664 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1665 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1666 gchar **std_err, gint *exit_status, GError **error)
1668 GString *output = std_out ? g_string_new(NULL) : NULL;
1669 GString *errors = std_err ? g_string_new(NULL) : NULL;
1670 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1672 if (std_out)
1673 *std_out = g_string_free(output, !result);
1675 if (std_err)
1676 *std_err = g_string_free(errors, !result);
1678 return result;
1683 * Wraps @c spawn_async(), which see.
1685 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1686 * @param argv The child's argument vector.
1687 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1688 * @param flags Ignored.
1689 * @param child_setup @girskip Ignored.
1690 * @param user_data Ignored.
1691 * @param child_pid @out @nullable The return location for child process ID, or @c NULL.
1692 * @param error The return location for error or @c NULL.
1694 * @return @c TRUE on success, @c FALSE if an error was set.
1696 GEANY_API_SYMBOL
1697 gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1698 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1699 GError **error)
1701 return spawn_async(dir, NULL, argv, env, child_pid, error);
1705 /* Returns "file:///" on Windows, "file://" everywhere else */
1706 const gchar *utils_get_uri_file_prefix(void)
1708 #ifdef G_OS_WIN32
1709 return "file:///";
1710 #else
1711 return "file://";
1712 #endif
1716 /* Retrieves the path for the given URI.
1717 * It returns:
1718 * - the path which was determined by g_filename_from_uri() or GIO
1719 * - NULL if the URI is non-local and gvfs-fuse is not installed
1720 * - a new copy of 'uri' if it is not an URI. */
1721 gchar *utils_get_path_from_uri(const gchar *uri)
1723 gchar *locale_filename;
1725 g_return_val_if_fail(uri != NULL, NULL);
1727 if (! utils_is_uri(uri))
1728 return g_strdup(uri);
1730 /* this will work only for 'file://' URIs */
1731 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1732 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1733 if (locale_filename == NULL)
1735 GFile *file = g_file_new_for_uri(uri);
1736 locale_filename = g_file_get_path(file);
1737 g_object_unref(file);
1738 if (locale_filename == NULL)
1740 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1741 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1745 return locale_filename;
1749 gboolean utils_is_uri(const gchar *uri)
1751 g_return_val_if_fail(uri != NULL, FALSE);
1753 return (strstr(uri, "://") != NULL);
1757 /* path should be in locale encoding */
1758 gboolean utils_is_remote_path(const gchar *path)
1760 g_return_val_if_fail(path != NULL, FALSE);
1762 /* if path is an URI and it doesn't start "file://", we take it as remote */
1763 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1764 return TRUE;
1766 #ifndef G_OS_WIN32
1768 static gchar *fuse_path = NULL;
1769 static gsize len = 0;
1771 if (G_UNLIKELY(fuse_path == NULL))
1773 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1774 len = strlen(fuse_path);
1776 /* Comparing the file path against a hardcoded path is not the most elegant solution
1777 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1778 * proper GFile objects for Fuse paths, but it only does in future GVFS
1779 * versions (gvfs 1.1.1). */
1780 return (strncmp(path, fuse_path, len) == 0);
1782 #endif
1784 return FALSE;
1788 /* Remove all relative and untidy elements from the path of @a filename.
1789 * @param filename must be a valid absolute path.
1790 * @see utils_get_real_path() - also resolves links. */
1791 void utils_tidy_path(gchar *filename)
1793 GString *str;
1794 const gchar *needle;
1795 gboolean preserve_double_backslash = FALSE;
1797 g_return_if_fail(g_path_is_absolute(filename));
1799 str = g_string_new(filename);
1801 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1802 preserve_double_backslash = TRUE;
1804 #ifdef G_OS_WIN32
1805 /* using MSYS we can get Unix-style separators */
1806 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1807 #endif
1808 /* replace "/./" and "//" */
1809 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1810 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1812 if (preserve_double_backslash)
1813 g_string_prepend(str, "\\");
1815 /* replace "/../" */
1816 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1817 while (1)
1819 const gchar *c = strstr(str->str, needle);
1820 if (c == NULL)
1821 break;
1822 else
1824 gssize pos, sub_len;
1826 pos = c - str->str;
1827 if (pos <= 3)
1828 break; /* bad path */
1830 /* replace "/../" */
1831 g_string_erase(str, pos, strlen(needle));
1832 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1834 /* search for last "/" before found "/../" */
1835 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1836 sub_len = pos - (c - str->str);
1837 if (! c)
1838 break; /* bad path */
1840 pos = c - str->str; /* position of previous "/" */
1841 g_string_erase(str, pos, sub_len);
1844 if (str->len <= strlen(filename))
1845 memcpy(filename, str->str, str->len + 1);
1846 else
1847 g_warn_if_reached();
1848 g_string_free(str, TRUE);
1853 * Removes characters from a string, in place.
1855 * @param string String to search.
1856 * @param chars Characters to remove.
1858 * @return @a string - return value is only useful when nesting function calls, e.g.:
1859 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1861 * @see @c g_strdelimit.
1863 GEANY_API_SYMBOL
1864 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1866 const gchar *r;
1867 gchar *w = string;
1869 g_return_val_if_fail(string, NULL);
1870 if (G_UNLIKELY(EMPTY(chars)))
1871 return string;
1873 foreach_str(r, string)
1875 if (!strchr(chars, *r))
1876 *w++ = *r;
1878 *w = 0x0;
1879 return string;
1883 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1884 GSList *utils_get_config_files(const gchar *subdir)
1886 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1887 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1888 GSList *syslist, *node;
1890 if (!list)
1892 utils_mkdir(path, FALSE);
1894 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1895 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1896 /* merge lists */
1897 list = g_slist_concat(list, syslist);
1899 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1900 /* remove duplicates (next to each other after sorting) */
1901 foreach_slist(node, list)
1903 if (node->next && utils_str_equal(node->next->data, node->data))
1905 GSList *old = node->next;
1907 g_free(old->data);
1908 node->next = old->next;
1909 g_slist_free1(old);
1912 g_free(path);
1913 return list;
1917 /* Suffix can be NULL or a string which should be appended to the Help URL like
1918 * an anchor link, e.g. "#some_anchor". */
1919 gchar *utils_get_help_url(const gchar *suffix)
1921 gchar *uri;
1922 const gchar *uri_file_prefix = utils_get_uri_file_prefix();
1923 gint skip = strlen(uri_file_prefix);
1925 uri = g_strconcat(uri_file_prefix, app->docdir, "/index.html", NULL);
1926 #ifdef G_OS_WIN32
1927 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1928 #endif
1930 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1931 { /* fall back to online documentation if it is not found on the hard disk */
1932 g_free(uri);
1933 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", PACKAGE_VERSION, "/index.html", NULL);
1936 if (suffix != NULL)
1938 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1941 return uri;
1945 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1947 const gchar **p;
1949 for (p = haystack; *p != NULL; ++p)
1951 if (utils_str_equal(*p, needle))
1952 return TRUE;
1954 return FALSE;
1959 * Copies the current environment into a new array.
1960 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1961 * All further arguments are key, value pairs of variables which should be added to
1962 * the environment.
1964 * The argument list must be @c NULL-terminated.
1966 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1967 * @param first_varname Name of the first variable to copy into the new array.
1968 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1970 * @return @transfer{full} The new environment array. Use @c g_strfreev() to free it.
1972 GEANY_API_SYMBOL
1973 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1975 gchar **result;
1976 gchar **p;
1977 gchar **env;
1978 va_list args;
1979 const gchar *key, *value;
1980 guint n, o;
1982 /* count the additional variables */
1983 va_start(args, first_varname);
1984 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1985 va_end(args);
1986 /* the passed arguments should be even (key, value pairs) */
1987 g_return_val_if_fail(o % 2 == 0, NULL);
1989 o /= 2;
1991 /* get all the environ variables */
1992 env = g_listenv();
1994 /* create an array large enough to hold the new environment */
1995 n = g_strv_length(env);
1996 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
1997 result = g_new(gchar *, n + o + 1);
1999 /* copy the environment */
2000 for (n = 0, p = env; *p != NULL; ++p)
2002 /* copy the variable */
2003 value = g_getenv(*p);
2004 if (G_LIKELY(value != NULL))
2006 /* skip excluded variables */
2007 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
2008 continue;
2010 result[n++] = g_strconcat(*p, "=", value, NULL);
2013 g_strfreev(env);
2015 /* now add additional variables */
2016 va_start(args, first_varname);
2017 key = first_varname;
2018 value = va_arg(args, gchar*);
2019 while (key != NULL)
2021 result[n++] = g_strconcat(key, "=", value, NULL);
2023 key = va_arg(args, gchar*);
2024 if (key == NULL)
2025 break;
2026 value = va_arg(args, gchar*);
2028 va_end(args);
2030 result[n] = NULL;
2032 return result;
2036 /* Joins @a first and @a second into a new string vector, freeing the originals.
2037 * The original contents are reused. */
2038 gchar **utils_strv_join(gchar **first, gchar **second)
2040 gchar **strv;
2041 gchar **rptr, **wptr;
2043 if (!first)
2044 return second;
2045 if (!second)
2046 return first;
2048 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2049 wptr = strv;
2051 foreach_strv(rptr, first)
2052 *wptr++ = *rptr;
2053 foreach_strv(rptr, second)
2054 *wptr++ = *rptr;
2056 g_free(first);
2057 g_free(second);
2058 return strv;
2061 /* * Returns the common prefix 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 (maybe empty), or NULL if an empty list
2069 * was passed in.
2071 GEANY_EXPORT_SYMBOL
2072 gchar *utils_strv_find_common_prefix(gchar **strv, gssize strv_len)
2074 gsize num;
2076 if (strv_len == 0)
2077 return NULL;
2079 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2081 for (gsize i = 0; strv[0][i]; i++)
2083 for (gsize j = 1; j < num; j++)
2085 if (strv[j][i] != strv[0][i])
2087 /* return prefix on first mismatch */
2088 return g_strndup(strv[0], i);
2093 return g_strdup(strv[0]);
2097 /* * Returns the longest common substring in a list of strings.
2099 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2101 * @param strv The list of strings to process.
2102 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2104 * @return The common prefix that is part of all strings.
2106 GEANY_EXPORT_SYMBOL
2107 gchar *utils_strv_find_lcs(gchar **strv, gssize strv_len, const gchar *delim)
2109 gchar *first, *_sub, *sub;
2110 gsize num;
2111 gsize n_chars;
2112 gsize len;
2113 gsize max = 0;
2114 char *lcs;
2115 gsize found;
2117 if (strv_len == 0)
2118 return NULL;
2120 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2122 first = strv[0];
2123 len = strlen(first);
2125 /* sub is the working area where substrings from first are copied to */
2126 sub = g_malloc(len+1);
2127 lcs = g_strdup("");
2128 foreach_str(_sub, first)
2130 gsize chars_left = len - (_sub - first);
2131 /* No point in continuing if the remainder is too short */
2132 if (max > chars_left)
2133 break;
2134 /* If delimiters are given, we only need to compare substrings which start and
2135 * end with one of them, so skip any non-delim chars at front ... */
2136 if (NZV(delim) && (strchr(delim, _sub[0]) == NULL))
2137 continue;
2138 for (n_chars = 1; n_chars <= chars_left; n_chars++)
2140 if (NZV(delim))
2141 { /* ... and advance to the next delim char at the end, if any */
2142 if (!_sub[n_chars] || strchr(delim, _sub[n_chars]) == NULL)
2143 continue;
2144 n_chars += 1;
2146 g_strlcpy(sub, _sub, n_chars+1);
2147 found = 1;
2148 for (gsize i = 1; i < num; i++)
2150 if (strstr(strv[i], sub) == NULL)
2151 break;
2152 found++;
2154 if (found == num && n_chars > max)
2156 max = n_chars;
2157 SETPTR(lcs, g_strdup(sub));
2161 g_free(sub);
2163 return lcs;
2167 /** Transform file names in a list to be shorter.
2169 * This function takes a list of file names (probably with absolute paths), and
2170 * transforms the paths such that they are short but still unique. This is intended
2171 * for dialogs which present the file list to the user, where the base name may result
2172 * in duplicates (showing the full path might be inappropriate).
2174 * The algorthm strips the common prefix (e-g. the user's home directory) and
2175 * replaces the longest common substring with an ellipsis ("...").
2177 * @param file_names @array{length=file_names_len} The list of strings to process.
2178 * @param file_names_len The number of strings contained in @a file_names. Can be -1 if it's
2179 * terminated by @c NULL.
2180 * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by
2181 @c NULL. Use @c g_strfreev() to free it.
2183 * @since 1.34 (API 239)
2185 GEANY_API_SYMBOL
2186 gchar **utils_strv_shorten_file_list(gchar **file_names, gssize file_names_len)
2188 gsize num;
2189 gsize i;
2190 gchar *prefix, *lcs, *end;
2191 gchar **names;
2192 gsize prefix_len = 0, lcs_len = 0;
2194 if (file_names_len == 0)
2195 return g_new0(gchar *, 1);
2197 g_return_val_if_fail(file_names != NULL, NULL);
2199 num = (file_names_len == -1) ? g_strv_length(file_names) : (gsize) file_names_len;
2200 /* Always include a terminating NULL, enables easy freeing with g_strfreev()
2201 * We just copy the pointers so we can advance them here. But don't
2202 * forget to duplicate the strings before returning.
2204 names = g_new(gchar *, num + 1);
2205 memcpy(names, file_names, num * sizeof(gchar *));
2206 /* Always include a terminating NULL, enables easy freeing with g_strfreev() */
2207 names[num] = NULL;
2209 /* First: determine the common prefix, that will be stripped.
2210 * We only want to strip full path components, including the trailing slash.
2211 * Except if the component is just "/".
2213 prefix = utils_strv_find_common_prefix(names, num);
2214 end = strrchr(prefix, G_DIR_SEPARATOR);
2215 if (end && end > prefix)
2217 prefix_len = end - prefix + 1; /* prefix_len includes the trailing slash */
2218 for (i = 0; i < num; i++)
2219 names[i] += prefix_len;
2222 /* Second: determine the longest common substring (lcs), that will be ellipsized. Again,
2223 * we look only for full path compnents so that we ellipsize between separators. This implies
2224 * that the file name cannot be ellipsized which is desirable anyway.
2226 lcs = utils_strv_find_lcs(names, num, G_DIR_SEPARATOR_S"/");
2227 if (lcs)
2229 lcs_len = strlen(lcs);
2230 /* Don't bother for tiny common parts (which are often just "." or "/"). Beware
2231 * that lcs includes the enclosing dir separators so the part must be at least 5 chars
2232 * to be eligible for ellipsizing.
2234 if (lcs_len < 7)
2235 lcs_len = 0;
2238 /* Last: build the shortened list of unique file names */
2239 for (i = 0; i < num; i++)
2241 if (lcs_len == 0)
2242 { /* no lcs, copy without prefix */
2243 names[i] = g_strdup(names[i]);
2245 else
2247 const gchar *lcs_start = strstr(names[i], lcs);
2248 const gchar *lcs_end = lcs_start + lcs_len;
2249 /* Dir seperators are included in lcs but shouldn't be elipsized. */
2250 names[i] = g_strdup_printf("%.*s...%s", (int)(lcs_start - names[i] + 1), names[i], lcs_end - 1);
2254 g_free(lcs);
2255 g_free(prefix);
2257 return names;
2261 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2262 * obviously g_date_set_parse() uses some magic.
2263 * The returned GDate object must be freed. */
2264 GDate *utils_parse_date(const gchar *input)
2266 GDate *date = g_date_new();
2268 g_date_set_parse(date, input);
2270 if (g_date_valid(date))
2271 return date;
2273 g_date_free(date);
2274 return NULL;
2278 gchar *utils_parse_and_format_build_date(const gchar *input)
2280 gchar date_buf[255];
2281 GDate *date = utils_parse_date(input);
2283 if (date != NULL)
2285 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2286 g_date_free(date);
2287 return g_strdup(date_buf);
2290 return g_strdup(input);
2294 gchar *utils_get_user_config_dir(void)
2296 #ifdef G_OS_WIN32
2297 return win32_get_user_config_dir();
2298 #else
2299 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2300 #endif
2304 static gboolean is_osx_bundle(void)
2306 #ifdef MAC_INTEGRATION
2307 gchar *bundle_id = gtkosx_application_get_bundle_id();
2308 if (bundle_id)
2310 g_free(bundle_id);
2311 return TRUE;
2313 #endif
2314 return FALSE;
2318 const gchar *utils_resource_dir(GeanyResourceDirType type)
2320 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2322 if (!resdirs[RESOURCE_DIR_DATA])
2324 #ifdef G_OS_WIN32
2325 gchar *prefix = win32_get_installation_dir();
2327 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", NULL);
2328 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2329 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2330 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2331 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2332 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2333 g_free(prefix);
2334 #else
2335 if (is_osx_bundle())
2337 # ifdef MAC_INTEGRATION
2338 gchar *prefix = gtkosx_application_get_resource_path();
2340 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2341 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2342 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2343 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2344 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2345 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2346 g_free(prefix);
2347 # endif
2349 else
2351 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2352 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2353 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2354 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2355 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2356 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(GEANY_LIBEXECDIR, "geany", NULL);
2358 #endif
2361 return resdirs[type];
2365 void utils_start_new_geany_instance(const gchar *doc_path)
2367 const gchar *command = is_osx_bundle() ? "open" : "geany";
2368 gchar *exec_path = g_find_program_in_path(command);
2370 if (exec_path)
2372 GError *err = NULL;
2373 const gchar *argv[6]; // max args + 1
2374 gint argc = 0;
2376 argv[argc++] = exec_path;
2377 if (is_osx_bundle())
2379 argv[argc++] = "-n";
2380 argv[argc++] = "-a";
2381 argv[argc++] = "Geany";
2382 argv[argc++] = doc_path;
2384 else
2386 argv[argc++] = "-i";
2387 argv[argc++] = doc_path;
2389 argv[argc] = NULL;
2391 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2393 g_printerr("Unable to open new window: %s\n", err->message);
2394 g_error_free(err);
2396 g_free(exec_path);
2398 else
2399 g_printerr("Unable to find 'geany'\n");
2404 * Get a link-dereferenced, absolute version of a file name.
2406 * This is similar to the POSIX `realpath` function when passed a
2407 * @c NULL argument.
2409 * @warning This function suffers the same problems as the POSIX
2410 * function `realpath()`, namely that it's impossible to determine
2411 * a suitable size for the returned buffer, and so it's limited to a
2412 * maximum of `PATH_MAX`.
2414 * @param file_name The file name to get the real path of.
2416 * @return A newly-allocated string containing the real path which
2417 * should be freed with `g_free()` when no longer needed, or @c NULL
2418 * if the real path cannot be obtained.
2420 * @since 1.32 (API 235)
2422 GEANY_API_SYMBOL
2423 gchar *utils_get_real_path(const gchar *file_name)
2425 return tm_get_real_path(file_name);
2430 * Get a string describing the OS.
2432 * If the OS can be determined, a string which describes the OS will
2433 * be returned. If no OS can be determined then `NULL` will be returned.
2435 * @note The format of the returned string is unspecified and is only
2436 * meant to provide diagnostic information to the user.
2438 * @return A newly-allocated string containing a description of the
2439 * OS if it can be determined or `NULL` if it cannot.
2441 * @since 1.37
2443 gchar *utils_get_os_info_string(void)
2445 gchar *os_info = NULL;
2447 #if GLIB_CHECK_VERSION(2, 64, 0)
2448 # if ! defined(__APPLE__)
2449 /* on non-macOS operating systems */
2451 GString *os_str;
2452 gchar *pretty_name;
2453 gchar *code_name;
2455 pretty_name = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
2456 if (pretty_name == NULL)
2457 return NULL;
2459 os_str = g_string_new(pretty_name);
2460 g_free(pretty_name);
2462 code_name = g_get_os_info(G_OS_INFO_KEY_VERSION_CODENAME);
2463 if (code_name != NULL)
2465 g_string_append_printf(os_str, " (%s)", code_name);
2466 g_free(code_name);
2469 os_info = g_string_free(os_str, FALSE);
2471 # else
2472 /* on macOS, only `G_OS_INFO_KEY_NAME` is supported and returns the
2473 * fixed string `macOS` */
2474 os_info = g_get_os_info(G_OS_INFO_KEY_NAME);
2475 # endif
2476 #else
2477 /* if g_get_os_info() is not available, do it the old-fashioned way */
2478 # if defined(_WIN64)
2479 os_info = g_strdup("Microsoft Windows (64-bit)");
2480 # elif defined(_WIN32)
2481 os_info = g_strdup("Microsoft Windows");
2482 # elif defined(__APPLE__)
2483 os_info = g_strdup("Apple macOS");
2484 # elif defined(__linux__)
2485 os_info = g_strdup("Linux");
2486 # elif defined(__FreeBSD__)
2487 os_info = g_strdup("FreeBSD");
2488 # elif defined(__ANDROID__)
2489 os_info = g_strdup("Android");
2490 # endif
2491 #endif
2493 return os_info;