Don't comment global content unless it is Conf
[geany-mirror.git] / src / utils.c
blob04eee046baa2cfbfdb22d4633b2f8cf94ef80738
1 /*
2 * utils.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2005 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * General utility functions, non-GTK related.
25 #ifdef HAVE_CONFIG_H
26 # include "config.h"
27 #endif
29 #include "utils.h"
31 #include "app.h"
32 #include "dialogs.h"
33 #include "document.h"
34 #include "prefs.h"
35 #include "prefix.h"
36 #include "sciwrappers.h"
37 #include "spawn.h"
38 #include "support.h"
39 #include "tm_source_file.h" // for tm_get_real_path()
40 #include "templates.h"
41 #include "ui_utils.h"
42 #include "win32.h"
43 #include "osx.h"
45 #include <stdlib.h>
46 #include <ctype.h>
47 #include <math.h>
48 #include <unistd.h>
49 #include <string.h>
50 #include <errno.h>
51 #include <stdarg.h>
53 #ifdef HAVE_SYS_STAT_H
54 # include <sys/stat.h>
55 #endif
56 #ifdef HAVE_SYS_TYPES_H
57 # include <sys/types.h>
58 #endif
60 #include <glib/gstdio.h>
61 #include <gio/gio.h>
64 /**
65 * Tries to open the given URI in a browser.
66 * On Windows, the system's default browser is opened.
67 * On non-Windows systems, the browser command can be set in the preferences dialog.
68 * If unset (empty) the system's default browser is used. If set it specifies the
69 * command to execute. Either way, if it fails the user is prompted to correct the
70 * pref.
72 * @param uri The URI to open in the web browser.
74 * @since 0.16
75 **/
76 GEANY_API_SYMBOL
77 void utils_open_browser(const gchar *uri)
79 #ifdef G_OS_WIN32
80 g_return_if_fail(uri != NULL);
81 win32_open_browser(uri);
82 #else
83 gchar *new_cmd, *argv[2] = { (gchar *) uri, NULL };
85 g_return_if_fail(uri != NULL);
87 while (1)
89 /* Uses the user's default browser akin to xdg-open (in flatpak through a portal) */
90 if (EMPTY(tool_prefs.browser_cmd))
92 if (gtk_show_uri_on_window(GTK_WINDOW(main_widgets.window), uri, GDK_CURRENT_TIME, NULL))
93 break;
95 else if (spawn_async(NULL, tool_prefs.browser_cmd, argv, NULL, NULL, NULL))
96 break;
98 /* Allow the user to correct the pref. new_cmd may become empty. */
99 new_cmd = dialogs_show_input(_("Select Browser"), GTK_WINDOW(main_widgets.window),
100 _("Failed to spawn the configured browser command. Please "
101 "enter a valid command or leave it empty in order "
102 "to spawn the system default browser."),
103 tool_prefs.browser_cmd);
105 if (new_cmd == NULL) /* user canceled */
106 break;
108 SETPTR(tool_prefs.browser_cmd, new_cmd);
110 #endif
114 /* taken from anjuta, to determine the EOL mode of the file */
115 gint utils_get_line_endings(const gchar* buffer, gsize size)
117 gsize i;
118 guint cr, lf, crlf, max_mode;
119 gint mode;
121 cr = lf = crlf = 0;
123 for (i = 0; i < size ; i++)
125 if (buffer[i] == 0x0a)
127 /* LF */
128 lf++;
130 else if (buffer[i] == 0x0d)
132 if (i >= (size - 1))
134 /* Last char, CR */
135 cr++;
137 else
139 if (buffer[i + 1] != 0x0a)
141 /* CR */
142 cr++;
144 else
146 /* CRLF */
147 crlf++;
149 i++;
154 /* Vote for the maximum */
155 mode = SC_EOL_LF;
156 max_mode = lf;
157 if (crlf > max_mode)
159 mode = SC_EOL_CRLF;
160 max_mode = crlf;
162 if (cr > max_mode)
164 mode = SC_EOL_CR;
165 max_mode = cr;
168 return mode;
172 gboolean utils_isbrace(gchar c, gboolean include_angles)
174 switch (c)
176 case '<':
177 case '>':
178 return include_angles;
180 case '(':
181 case ')':
182 case '{':
183 case '}':
184 case '[':
185 case ']': return TRUE;
186 default: return FALSE;
191 gboolean utils_is_opening_brace(gchar c, gboolean include_angles)
193 switch (c)
195 case '<':
196 return include_angles;
198 case '(':
199 case '{':
200 case '[': return TRUE;
201 default: return FALSE;
207 * Writes @a text into a file named @a filename.
208 * If the file doesn't exist, it will be created.
209 * If it already exists, it will be overwritten.
211 * @warning You should use @c g_file_set_contents() instead if you don't need
212 * file permissions and other metadata to be preserved, as that always handles
213 * disk exhaustion safely.
215 * @param filename The filename of the file to write, in locale encoding.
216 * @param text The text to write into the file.
218 * @return 0 if the file was successfully written, otherwise the @c errno of the
219 * failed operation is returned.
221 GEANY_API_SYMBOL
222 gint utils_write_file(const gchar *filename, const gchar *text)
224 g_return_val_if_fail(filename != NULL, ENOENT);
225 g_return_val_if_fail(text != NULL, EINVAL);
227 if (file_prefs.use_safe_file_saving)
229 GError *error = NULL;
230 if (! g_file_set_contents(filename, text, -1, &error))
232 geany_debug("%s: could not write to file %s (%s)", G_STRFUNC, filename, error->message);
233 g_error_free(error);
234 return EIO;
237 else
239 FILE *fp;
240 gsize bytes_written, len;
241 gboolean fail = FALSE;
243 if (filename == NULL)
244 return ENOENT;
246 len = strlen(text);
247 errno = 0;
248 fp = g_fopen(filename, "w");
249 if (fp == NULL)
250 fail = TRUE;
251 else
253 bytes_written = fwrite(text, sizeof(gchar), len, fp);
255 if (len != bytes_written)
257 fail = TRUE;
258 geany_debug(
259 "utils_write_file(): written only %"G_GSIZE_FORMAT" bytes, had to write %"G_GSIZE_FORMAT" bytes to %s",
260 bytes_written, len, filename);
262 if (fclose(fp) != 0)
263 fail = TRUE;
265 if (fail)
267 geany_debug("utils_write_file(): could not write to file %s (%s)",
268 filename, g_strerror(errno));
269 return FALLBACK(errno, EIO);
272 return 0;
276 /** Searches backward through @a size bytes looking for a '<'.
277 * @param sel .
278 * @param size .
279 * @return @nullable The tag name (newly allocated) or @c NULL if no opening tag was found.
281 GEANY_API_SYMBOL
282 gchar *utils_find_open_xml_tag(const gchar sel[], gint size)
284 const gchar *cur, *begin;
285 gsize len;
287 cur = utils_find_open_xml_tag_pos(sel, size);
288 if (cur == NULL)
289 return NULL;
291 cur++; /* skip the bracket */
292 begin = cur;
293 while (strchr(":_-.", *cur) || isalnum(*cur))
294 cur++;
296 len = (gsize)(cur - begin);
297 return len ? g_strndup(begin, len) : NULL;
301 /** Searches backward through @a size bytes looking for a '<'.
302 * @param sel .
303 * @param size .
304 * @return @nullable pointer to '<' of the found opening tag within @a sel, or @c NULL if no opening tag was found.
306 GEANY_API_SYMBOL
307 const gchar *utils_find_open_xml_tag_pos(const gchar sel[], gint size)
309 /* stolen from anjuta and modified */
310 const gchar *begin, *cur;
312 if (G_UNLIKELY(size < 3))
313 { /* Smallest tag is "<p>" which is 3 characters */
314 return NULL;
316 begin = &sel[0];
317 cur = &sel[size - 1];
319 /* Skip to the character before the closing brace */
320 while (cur > begin)
322 if (*cur == '>')
323 break;
324 --cur;
326 --cur;
327 /* skip whitespace */
328 while (cur > begin && isspace(*cur))
329 cur--;
330 if (*cur == '/')
331 return NULL; /* we found a short tag which doesn't need to be closed */
332 while (cur > begin)
334 if (*cur == '<')
335 break;
336 /* exit immediately if such non-valid XML/HTML is detected, e.g. "<script>if a >" */
337 else if (*cur == '>')
338 break;
339 --cur;
342 /* if the found tag is an opening, not a closing tag or empty <> */
343 if (*cur == '<' && *(cur + 1) != '/' && *(cur + 1) != '>')
344 return cur;
346 return NULL;
350 /* Returns true if tag_name is a self-closing tag */
351 gboolean utils_is_short_html_tag(const gchar *tag_name)
353 const gchar names[][20] = {
354 "area",
355 "base",
356 "basefont", /* < or not < */
357 "br",
358 "col",
359 "command",
360 "embed",
361 "frame",
362 "hr",
363 "img",
364 "input",
365 "keygen",
366 "link",
367 "meta",
368 "param",
369 "source",
370 "track",
371 "wbr"
374 if (tag_name)
376 if (bsearch(tag_name, names, G_N_ELEMENTS(names), 20,
377 (GCompareFunc)g_ascii_strcasecmp))
378 return TRUE;
380 return FALSE;
384 const gchar *utils_get_eol_name(gint eol_mode)
386 switch (eol_mode)
388 case SC_EOL_CRLF: return _("Windows (CRLF)"); break;
389 case SC_EOL_CR: return _("Classic Mac (CR)"); break;
390 default: return _("Unix (LF)"); break;
395 const gchar *utils_get_eol_short_name(gint eol_mode)
397 switch (eol_mode)
399 case SC_EOL_CRLF: return _("CRLF"); break;
400 case SC_EOL_CR: return _("CR"); break;
401 default: return _("LF"); break;
406 const gchar *utils_get_eol_char(gint eol_mode)
408 switch (eol_mode)
410 case SC_EOL_CRLF: return "\r\n"; break;
411 case SC_EOL_CR: return "\r"; break;
412 default: return "\n"; break;
417 /* Converts line endings to @a target_eol_mode. */
418 void utils_ensure_same_eol_characters(GString *string, gint target_eol_mode)
420 const gchar *eol_str = utils_get_eol_char(target_eol_mode);
422 /* first convert data to LF only */
423 utils_string_replace_all(string, "\r\n", "\n");
424 utils_string_replace_all(string, "\r", "\n");
426 if (target_eol_mode == SC_EOL_LF)
427 return;
429 /* now convert to desired line endings */
430 utils_string_replace_all(string, "\n", eol_str);
434 gboolean utils_atob(const gchar *str)
436 if (G_UNLIKELY(str == NULL))
437 return FALSE;
438 else if (strcmp(str, "TRUE") == 0 || strcmp(str, "true") == 0)
439 return TRUE;
440 return FALSE;
444 /* NULL-safe version of g_path_is_absolute(). */
445 gboolean utils_is_absolute_path(const gchar *path)
447 if (G_UNLIKELY(EMPTY(path)))
448 return FALSE;
450 return g_path_is_absolute(path);
454 /* Skips root if path is absolute, do nothing otherwise.
455 * This is a relative-safe version of g_path_skip_root().
457 const gchar *utils_path_skip_root(const gchar *path)
459 const gchar *path_relative;
461 path_relative = g_path_skip_root(path);
463 return (path_relative != NULL) ? path_relative : path;
467 /* Convert a fractional @a val in the range [0, 1] to a whole value in the range [0, @a factor].
468 * In particular, this is used for converting a @c GdkColor to the "#RRGGBB" format in a way that
469 * agrees with GTK+, so the "#RRGGBB" in the color picker is the same "#RRGGBB" that is inserted
470 * into the document. See https://github.com/geany/geany/issues/1527
472 gdouble utils_scale_round(gdouble val, gdouble factor)
474 val = floor(val * factor + 0.5);
475 val = MAX(val, 0);
476 val = MIN(val, factor);
478 return val;
482 /* like g_utf8_strdown() but if @str is not valid UTF8, convert it from locale first.
483 * returns NULL on charset conversion failure */
484 gchar *utils_utf8_strdown(const gchar *str)
486 gchar *down;
488 if (g_utf8_validate(str, -1, NULL))
489 down = g_utf8_strdown(str, -1);
490 else
492 down = g_locale_to_utf8(str, -1, NULL, NULL, NULL);
493 if (down)
494 SETPTR(down, g_utf8_strdown(down, -1));
497 return down;
502 * A replacement function for g_strncasecmp() to compare strings case-insensitive.
503 * It converts both strings into lowercase using g_utf8_strdown() and then compare
504 * both strings using strcmp().
505 * This is not completely accurate regarding locale-specific case sorting rules
506 * but seems to be a good compromise between correctness and performance.
508 * The input strings should be in UTF-8 or locale encoding.
510 * @param s1 @nullable Pointer to first string or @c NULL.
511 * @param s2 @nullable Pointer to second string or @c NULL.
513 * @return an integer less than, equal to, or greater than zero if @a s1 is found, respectively,
514 * to be less than, to match, or to be greater than @a s2.
516 * @since 0.16
518 GEANY_API_SYMBOL
519 gint utils_str_casecmp(const gchar *s1, const gchar *s2)
521 gchar *tmp1, *tmp2;
522 gint result;
524 g_return_val_if_fail(s1 != NULL, 1);
525 g_return_val_if_fail(s2 != NULL, -1);
527 /* ensure strings are UTF-8 and lowercase */
528 tmp1 = utils_utf8_strdown(s1);
529 if (! tmp1)
530 return 1;
531 tmp2 = utils_utf8_strdown(s2);
532 if (! tmp2)
534 g_free(tmp1);
535 return -1;
538 /* compare */
539 result = strcmp(tmp1, tmp2);
541 g_free(tmp1);
542 g_free(tmp2);
543 return result;
548 * Truncates the input string to a given length.
549 * Characters are removed from the middle of the string, so the start and the end of string
550 * won't change.
552 * @param string Input string.
553 * @param truncate_length The length in characters of the resulting string.
555 * @return A copy of @a string which is truncated to @a truncate_length characters,
556 * should be freed when no longer needed.
558 * @since 0.17
560 /* This following function is taken from Gedit. */
561 GEANY_API_SYMBOL
562 gchar *utils_str_middle_truncate(const gchar *string, guint truncate_length)
564 GString *truncated;
565 guint length;
566 guint n_chars;
567 guint num_left_chars;
568 guint right_offset;
569 guint delimiter_length;
570 const gchar *delimiter = "\342\200\246";
572 g_return_val_if_fail(string != NULL, NULL);
574 length = strlen(string);
576 g_return_val_if_fail(g_utf8_validate(string, length, NULL), NULL);
578 /* It doesn't make sense to truncate strings to less than the size of the delimiter plus 2
579 * characters (one on each side) */
580 delimiter_length = g_utf8_strlen(delimiter, -1);
581 if (truncate_length < (delimiter_length + 2))
582 return g_strdup(string);
584 n_chars = g_utf8_strlen(string, length);
586 /* Make sure the string is not already small enough. */
587 if (n_chars <= truncate_length)
588 return g_strdup (string);
590 /* Find the 'middle' where the truncation will occur. */
591 num_left_chars = (truncate_length - delimiter_length) / 2;
592 right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
594 truncated = g_string_new_len(string, g_utf8_offset_to_pointer(string, num_left_chars) - string);
595 g_string_append(truncated, delimiter);
596 g_string_append(truncated, g_utf8_offset_to_pointer(string, right_offset));
598 return g_string_free(truncated, FALSE);
603 * @c NULL-safe string comparison. Returns @c TRUE if both @a a and @a b are @c NULL
604 * or if @a a and @a b refer to valid strings which are equal.
606 * @param a @nullable Pointer to first string or @c NULL.
607 * @param b @nullable Pointer to second string or @c NULL.
609 * @return @c TRUE if @a a equals @a b, else @c FALSE.
611 GEANY_API_SYMBOL
612 gboolean utils_str_equal(const gchar *a, const gchar *b)
614 /* (taken from libexo from os-cillation) */
615 if (a == NULL && b == NULL) return TRUE;
616 else if (a == NULL || b == NULL) return FALSE;
618 return strcmp(a, b) == 0;
623 * Removes the extension from @a filename and return the result in a newly allocated string.
625 * @param filename The filename to operate on.
627 * @return A newly-allocated string, should be freed when no longer needed.
629 GEANY_API_SYMBOL
630 gchar *utils_remove_ext_from_filename(const gchar *filename)
632 gchar *last_dot;
633 gchar *result;
634 gsize len;
636 g_return_val_if_fail(filename != NULL, NULL);
638 last_dot = strrchr(filename, '.');
639 if (! last_dot)
640 return g_strdup(filename);
642 len = (gsize) (last_dot - filename);
643 result = g_malloc(len + 1);
644 memcpy(result, filename, len);
645 result[len] = 0;
647 return result;
651 gchar utils_brace_opposite(gchar ch)
653 switch (ch)
655 case '(': return ')';
656 case ')': return '(';
657 case '[': return ']';
658 case ']': return '[';
659 case '{': return '}';
660 case '}': return '{';
661 case '<': return '>';
662 case '>': return '<';
663 default: return '\0';
668 /* Checks whether the given file can be written. locale_filename is expected in locale encoding.
669 * Returns 0 if it can be written, otherwise it returns errno */
670 gint utils_is_file_writable(const gchar *locale_filename)
672 gchar *file;
673 gint ret;
675 if (! g_file_test(locale_filename, G_FILE_TEST_EXISTS) &&
676 ! g_file_test(locale_filename, G_FILE_TEST_IS_DIR))
677 /* get the file's directory to check for write permission if it doesn't yet exist */
678 file = g_path_get_dirname(locale_filename);
679 else
680 file = g_strdup(locale_filename);
682 #ifdef G_OS_WIN32
683 /* use _waccess on Windows, access() doesn't accept special characters */
684 ret = win32_check_write_permission(file);
685 #else
687 /* access set also errno to "FILE NOT FOUND" even if locale_filename is writeable, so use
688 * errno only when access() explicitly returns an error */
689 if (access(file, R_OK | W_OK) != 0)
690 ret = errno;
691 else
692 ret = 0;
693 #endif
694 g_free(file);
695 return ret;
699 /* Replaces all occurrences of needle in haystack with replacement.
700 * Warning: *haystack must be a heap address; it may be freed and reassigned.
701 * Note: utils_string_replace_all() will always be faster when @a replacement is longer
702 * than @a needle.
703 * All strings have to be NULL-terminated.
704 * See utils_string_replace_all() for details. */
705 void utils_str_replace_all(gchar **haystack, const gchar *needle, const gchar *replacement)
707 GString *str;
709 g_return_if_fail(*haystack != NULL);
711 str = g_string_new(*haystack);
713 g_free(*haystack);
714 utils_string_replace_all(str, needle, replacement);
716 *haystack = g_string_free(str, FALSE);
720 gint utils_strpos(const gchar *haystack, const gchar *needle)
722 const gchar *sub;
724 if (! *needle)
725 return -1;
727 sub = strstr(haystack, needle);
728 if (! sub)
729 return -1;
731 return sub - haystack;
736 * Retrieves a formatted date/time string from GDateTime.
737 * This function works on UTF-8 encoded strings.
739 * @param format The format string to pass to g_date_time_format, in UTF-8 encoding.
740 See https://docs.gtk.org/glib/method.DateTime.format.html for details.
741 * @param time_to_use @nullable The date/time to use, in time_t format or @c NULL to use the current time.
743 * @return A newly-allocated string, should be freed when no longer needed.
745 * @since 0.16
747 GEANY_API_SYMBOL
748 gchar *utils_get_date_time(const gchar *format, time_t *time_to_use)
750 time_t unixtime;
751 gchar *datetime_formatted;
752 GDateTime *datetime;
754 g_return_val_if_fail(format != NULL, NULL);
756 if (time_to_use != NULL)
757 unixtime = *time_to_use;
758 else
759 unixtime = time(NULL);
761 datetime = g_date_time_new_from_unix_local(unixtime);
762 datetime_formatted = g_date_time_format(datetime, format);
764 g_date_time_unref(datetime);
765 return datetime_formatted;
769 gchar *utils_get_initials(const gchar *name)
771 gint i = 1, j = 1;
772 gchar *initials = g_malloc0(5);
774 initials[0] = name[0];
775 while (name[i] != '\0' && j < 4)
777 if (name[i] == ' ' && name[i + 1] != ' ')
779 initials[j++] = name[i + 1];
781 i++;
783 return initials;
788 * Wraps g_key_file_get_integer() to add a default value argument.
790 * @param config A GKeyFile object.
791 * @param section The group name to look in for the key.
792 * @param key The key to find.
793 * @param default_value The default value which will be returned when @a section or @a key
794 * don't exist.
796 * @return The value associated with @a key as an integer, or the given default value if the value
797 * could not be retrieved.
799 GEANY_API_SYMBOL
800 gint utils_get_setting_integer(GKeyFile *config, const gchar *section, const gchar *key,
801 const gint default_value)
803 gint tmp;
804 GError *error = NULL;
806 g_return_val_if_fail(config, default_value);
808 tmp = g_key_file_get_integer(config, section, key, &error);
809 if (error)
811 g_error_free(error);
812 return default_value;
814 return tmp;
819 * Wraps g_key_file_get_boolean() to add a default value argument.
821 * @param config A GKeyFile object.
822 * @param section The group name to look in for the key.
823 * @param key The key to find.
824 * @param default_value The default value which will be returned when @c section or @c key
825 * don't exist.
827 * @return The value associated with @a key as a boolean, or the given default value if the value
828 * could not be retrieved.
830 GEANY_API_SYMBOL
831 gboolean utils_get_setting_boolean(GKeyFile *config, const gchar *section, const gchar *key,
832 const gboolean default_value)
834 gboolean tmp;
835 GError *error = NULL;
837 g_return_val_if_fail(config, default_value);
839 tmp = g_key_file_get_boolean(config, section, key, &error);
840 if (error)
842 g_error_free(error);
843 return default_value;
845 return tmp;
850 * Wraps g_key_file_get_double() to add a default value argument.
852 * @param config A GKeyFile object.
853 * @param section The group name to look in for the key.
854 * @param key The key to find.
855 * @param default_value The default value which will be returned when @a section or @a key
856 * don't exist.
858 * @return The value associated with @a key as an integer, or the given default value if the value
859 * could not be retrieved.
861 GEANY_API_SYMBOL
862 gdouble utils_get_setting_double(GKeyFile *config, const gchar *section, const gchar *key,
863 const gdouble default_value)
865 gdouble tmp;
866 GError *error = NULL;
868 g_return_val_if_fail(config, default_value);
870 tmp = g_key_file_get_double(config, section, key, &error);
871 if (error)
873 g_error_free(error);
874 return default_value;
876 return tmp;
881 * Wraps g_key_file_get_string() to add a default value argument.
883 * @param config A GKeyFile object.
884 * @param section The group name to look in for the key.
885 * @param key The key to find.
886 * @param default_value The default value which will be returned when @a section or @a key
887 * don't exist.
889 * @return A newly allocated string, either the value for @a key or a copy of the given
890 * default value if it could not be retrieved.
892 GEANY_API_SYMBOL
893 gchar *utils_get_setting_string(GKeyFile *config, const gchar *section, const gchar *key,
894 const gchar *default_value)
896 gchar *tmp;
898 g_return_val_if_fail(config, g_strdup(default_value));
900 tmp = g_key_file_get_string(config, section, key, NULL);
901 if (!tmp)
903 return g_strdup(default_value);
905 return tmp;
909 gchar *utils_get_hex_from_color(GdkColor *color)
911 g_return_val_if_fail(color != NULL, NULL);
913 return g_strdup_printf("#%02X%02X%02X",
914 (guint) (utils_scale_round(color->red / 65535.0, 255)),
915 (guint) (utils_scale_round(color->green / 65535.0, 255)),
916 (guint) (utils_scale_round(color->blue / 65535.0, 255)));
920 /* Get directory from current file in the notebook.
921 * Returns dir string that should be freed or NULL, depending on whether current file is valid.
922 * Returned string is in UTF-8 encoding */
923 gchar *utils_get_current_file_dir_utf8(void)
925 GeanyDocument *doc = document_get_current();
927 if (doc != NULL)
929 /* get current filename */
930 const gchar *cur_fname = doc->file_name;
932 if (cur_fname != NULL)
934 /* get folder part from current filename */
935 return g_path_get_dirname(cur_fname); /* returns "." if no path */
939 return NULL; /* no file open */
943 /* very simple convenience function */
944 void utils_beep(void)
946 if (prefs.beep_on_errors)
947 gdk_beep();
951 /* taken from busybox, thanks */
952 gchar *utils_make_human_readable_str(guint64 size, gulong block_size,
953 gulong display_unit)
955 /* The code will adjust for additional (appended) units. */
956 static const gchar zero_and_units[] = { '0', 0, 'K', 'M', 'G', 'T' };
957 static const gchar fmt[] = "%Lu %c%c";
958 static const gchar fmt_tenths[] = "%Lu.%d %c%c";
960 guint64 val;
961 gint frac;
962 const gchar *u;
963 const gchar *f;
965 u = zero_and_units;
966 f = fmt;
967 frac = 0;
969 val = size * block_size;
970 if (val == 0)
971 return g_strdup(u);
973 if (display_unit)
975 val += display_unit/2; /* Deal with rounding. */
976 val /= display_unit; /* Don't combine with the line above!!! */
978 else
980 ++u;
981 while ((val >= 1024) && (u < zero_and_units + sizeof(zero_and_units) - 1))
983 f = fmt_tenths;
984 ++u;
985 frac = ((((gint)(val % 1024)) * 10) + (1024 / 2)) / 1024;
986 val /= 1024;
988 if (frac >= 10)
989 { /* We need to round up here. */
990 ++val;
991 frac = 0;
995 /* If f==fmt then 'frac' and 'u' are ignored. */
996 return g_strdup_printf(f, val, frac, *u, 'b');
1000 /* converts a color representation using gdk_color_parse(), with additional
1001 * support of the "0x" prefix as a synonym for "#" */
1002 gboolean utils_parse_color(const gchar *spec, GdkColor *color)
1004 gchar buf[64] = {0};
1006 g_return_val_if_fail(spec != NULL, -1);
1008 if (spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X'))
1010 /* convert to # format for GDK to understand it */
1011 buf[0] = '#';
1012 strncpy(buf + 1, spec + 2, sizeof(buf) - 2);
1013 spec = buf;
1016 return gdk_color_parse(spec, color);
1020 /* converts a GdkColor to the packed 24 bits BGR format, as understood by Scintilla
1021 * returns a 24 bits BGR color, or -1 on failure */
1022 gint utils_color_to_bgr(const GdkColor *c)
1024 g_return_val_if_fail(c != NULL, -1);
1025 return (c->red / 256) | ((c->green / 256) << 8) | ((c->blue / 256) << 16);
1029 /* parses @p spec using utils_parse_color() and convert it to 24 bits BGR using
1030 * utils_color_to_bgr() */
1031 gint utils_parse_color_to_bgr(const gchar *spec)
1033 GdkColor color;
1034 if (utils_parse_color(spec, &color))
1035 return utils_color_to_bgr(&color);
1036 else
1037 return -1;
1041 /* Returns: newly allocated string with the current time formatted HH:MM:SS.
1042 * If "include_microseconds" is TRUE, microseconds are appended.
1044 * The returned string should be freed with g_free(). */
1045 gchar *utils_get_current_time_string(gboolean include_microseconds)
1047 // "%f" specifier for microseconds is only available since GLib 2.66
1048 if (glib_check_version(2, 66, 0) != NULL)
1049 include_microseconds = FALSE;
1051 GDateTime *now = g_date_time_new_now_local();
1052 const gchar *format = include_microseconds ? "%H:%M:%S.%f" : "%H:%M:%S";
1053 gchar *time_string = g_date_time_format(now, format);
1054 g_date_time_unref(now);
1055 return time_string;
1059 GIOChannel *utils_set_up_io_channel(
1060 gint fd, GIOCondition cond, gboolean nblock, GIOFunc func, gpointer data)
1062 GIOChannel *ioc;
1063 /*const gchar *encoding;*/
1065 #ifdef G_OS_WIN32
1066 ioc = g_io_channel_win32_new_fd(fd);
1067 #else
1068 ioc = g_io_channel_unix_new(fd);
1069 #endif
1071 if (nblock)
1072 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
1074 g_io_channel_set_encoding(ioc, NULL, NULL);
1076 if (! g_get_charset(&encoding))
1077 { // hope this works reliably
1078 GError *error = NULL;
1079 g_io_channel_set_encoding(ioc, encoding, &error);
1080 if (error)
1082 geany_debug("%s: %s", G_STRFUNC, error->message);
1083 g_error_free(error);
1084 return ioc;
1088 /* "auto-close" ;-) */
1089 g_io_channel_set_close_on_unref(ioc, TRUE);
1091 g_io_add_watch(ioc, cond, func, data);
1092 g_io_channel_unref(ioc);
1094 return ioc;
1098 /* Contributed by Stefan Oltmanns, thanks.
1099 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1100 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1101 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1103 gsize i, j, len;
1104 guint unicodechar;
1106 g_return_val_if_fail(string != NULL, FALSE);
1108 j = 0;
1109 len = strlen(string);
1110 for (i = 0; i < len; i++)
1112 if (string[i]=='\\')
1114 if (i++ >= strlen(string))
1116 return FALSE;
1118 switch (string[i])
1120 case '\\':
1121 if (keep_backslash)
1122 string[j++] = '\\';
1123 string[j] = '\\';
1124 break;
1125 case 'n':
1126 string[j] = '\n';
1127 break;
1128 case 'r':
1129 string[j] = '\r';
1130 break;
1131 case 't':
1132 string[j] = '\t';
1133 break;
1134 #if 0
1135 case 'x': /* Warning: May produce illegal utf-8 string! */
1136 i += 2;
1137 if (i >= strlen(string))
1139 return FALSE;
1141 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1142 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1143 else return FALSE;
1144 string[j] <<= 4;
1145 if (isdigit(string[i])) string[j] |= string[i] - 48;
1146 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1147 else return FALSE;
1148 break;
1149 #endif
1150 case 'u':
1152 i += 2;
1153 if (i >= strlen(string))
1155 return FALSE;
1157 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1158 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1159 else return FALSE;
1160 unicodechar <<= 4;
1161 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1162 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1163 else return FALSE;
1164 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1165 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1167 i += 2;
1168 unicodechar <<= 8;
1169 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1170 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1171 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1172 else unicodechar |= tolower(string[i])-87;
1174 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1175 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1177 i += 2;
1178 unicodechar <<= 8;
1179 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1180 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1181 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1182 else unicodechar |= tolower(string[i])-87;
1184 if (unicodechar < 0x80)
1186 string[j] = unicodechar;
1188 else if (unicodechar < 0x800)
1190 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1191 j++;
1192 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1194 else if (unicodechar < 0x10000)
1196 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1197 j++;
1198 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1199 j++;
1200 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1202 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1204 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1205 j++;
1206 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1207 j++;
1208 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1209 j++;
1210 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1212 else
1214 return FALSE;
1216 break;
1218 default:
1219 /* unnecessary escapes are allowed */
1220 if (keep_backslash)
1221 string[j++] = '\\';
1222 string[j] = string[i];
1225 else
1227 string[j] = string[i];
1229 j++;
1231 while (j < i)
1233 string[j] = 0;
1234 j++;
1236 return TRUE;
1240 /* Wraps a string in place, replacing a space with a newline character.
1241 * wrapstart is the minimum position to start wrapping or -1 for default */
1242 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1244 gchar *pos, *linestart;
1245 gboolean ret = FALSE;
1247 if (wrapstart < 0)
1248 wrapstart = 80;
1250 for (pos = linestart = string; *pos != '\0'; pos++)
1252 if (pos - linestart >= wrapstart && *pos == ' ')
1254 *pos = '\n';
1255 linestart = pos;
1256 ret = TRUE;
1259 return ret;
1264 * Converts the given UTF-8 encoded string into locale encoding.
1265 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1267 * @param utf8_text UTF-8 encoded text.
1269 * @return The converted string in locale encoding, or a copy of the input string if conversion
1270 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1272 GEANY_API_SYMBOL
1273 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1275 #ifdef G_OS_WIN32
1276 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1277 * which would result in wrongly converted strings */
1278 return g_strdup(utf8_text);
1279 #else
1280 gchar *locale_text;
1282 if (! utf8_text)
1283 return NULL;
1284 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1285 if (locale_text == NULL)
1286 locale_text = g_strdup(utf8_text);
1287 return locale_text;
1288 #endif
1293 * Converts the given string (in locale encoding) into UTF-8 encoding.
1294 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1296 * @param locale_text Text in locale encoding.
1298 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1299 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1301 GEANY_API_SYMBOL
1302 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1304 #ifdef G_OS_WIN32
1305 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1306 * which would result in wrongly converted strings */
1307 return g_strdup(locale_text);
1308 #else
1309 gchar *utf8_text;
1311 if (! locale_text)
1312 return NULL;
1313 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1314 if (utf8_text == NULL)
1315 utf8_text = g_strdup(locale_text);
1316 return utf8_text;
1317 #endif
1321 /* Pass pointers to free after arg_count.
1322 * The last argument must be NULL as an extra check that arg_count is correct. */
1323 void utils_free_pointers(gsize arg_count, ...)
1325 va_list a;
1326 gsize i;
1327 gpointer ptr;
1329 va_start(a, arg_count);
1330 for (i = 0; i < arg_count; i++)
1332 ptr = va_arg(a, gpointer);
1333 g_free(ptr);
1335 ptr = va_arg(a, gpointer);
1336 if (ptr)
1337 g_warning("Wrong arg_count!");
1338 va_end(a);
1342 /* Creates a string array deep copy of a series of non-NULL strings.
1343 * The first argument is nothing special and must not be NULL.
1344 * The list must be terminated with NULL. */
1345 GEANY_EXPORT_SYMBOL
1346 gchar **utils_strv_new(const gchar *first, ...)
1348 gsize strvlen, i;
1349 va_list args;
1350 gchar *str;
1351 gchar **strv;
1353 g_return_val_if_fail(first != NULL, NULL);
1355 strvlen = 1; /* for first argument */
1357 /* count other arguments */
1358 va_start(args, first);
1359 for (; va_arg(args, gchar*) != NULL; strvlen++);
1360 va_end(args);
1362 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1363 strv[0] = g_strdup(first);
1365 va_start(args, first);
1366 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1368 strv[i] = g_strdup(str);
1370 va_end(args);
1372 strv[i] = NULL;
1373 return strv;
1378 * Creates a directory if it doesn't already exist.
1379 * Creates intermediate parent directories as needed, too.
1380 * The permissions of the created directory are set 0700.
1382 * @param path The path of the directory to create, in locale encoding.
1383 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1385 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1386 * failed operation is returned.
1388 GEANY_API_SYMBOL
1389 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1391 gint mode = 0700;
1392 gint result;
1394 if (path == NULL || strlen(path) == 0)
1395 return EFAULT;
1397 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1398 if (result != 0)
1399 return errno;
1400 return 0;
1405 * Gets a list of files from the specified directory.
1406 * Locale encoding is expected for @a path and used for the file list. The list and the data
1407 * in the list should be freed after use, e.g.:
1408 * @code
1409 * g_slist_foreach(list, (GFunc) g_free, NULL);
1410 * g_slist_free(list); @endcode
1412 * @note If you don't need a list you should use the foreach_dir() macro instead -
1413 * it's more efficient.
1415 * @param path The path of the directory to scan, in locale encoding.
1416 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1417 * will use more memory.
1418 * @param sort Whether to sort alphabetically (UTF-8 safe).
1419 * @param error The location for storing a possible error, or @c NULL.
1421 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL if
1422 * no files were found. The list and its data should be freed when no longer needed.
1423 * @see utils_get_file_list().
1425 GEANY_API_SYMBOL
1426 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1428 GSList *list = NULL;
1429 GDir *dir;
1430 const gchar *filename;
1432 if (error)
1433 *error = NULL;
1434 g_return_val_if_fail(path != NULL, NULL);
1436 dir = g_dir_open(path, 0, error);
1437 if (dir == NULL)
1438 return NULL;
1440 foreach_dir(filename, dir)
1442 list = g_slist_prepend(list, full_path ?
1443 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1445 g_dir_close(dir);
1446 /* sorting last is quicker than on insertion */
1447 if (sort)
1448 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1449 return list;
1454 * Gets a sorted list of files from the specified directory.
1455 * Locale encoding is expected for @a path and used for the file list. The list and the data
1456 * in the list should be freed after use, e.g.:
1457 * @code
1458 * g_slist_foreach(list, (GFunc) g_free, NULL);
1459 * g_slist_free(list); @endcode
1461 * @param path The path of the directory to scan, in locale encoding.
1462 * @param length The location to store the number of non-@c NULL data items in the list,
1463 * unless @c NULL.
1464 * @param error The location for storing a possible error, or @c NULL.
1466 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL
1467 * if no files were found. The list and its data should be freed when no longer needed.
1468 * @see utils_get_file_list_full().
1470 GEANY_API_SYMBOL
1471 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1473 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1475 if (length)
1476 *length = g_slist_length(list);
1477 return list;
1481 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1482 gboolean utils_str_has_upper(const gchar *str)
1484 gunichar c;
1486 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1487 return FALSE;
1489 while (*str != '\0')
1491 c = g_utf8_get_char(str);
1492 /* check only letters and stop once the first non-capital was found */
1493 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1494 return TRUE;
1495 /* FIXME don't write a const string */
1496 str = g_utf8_next_char(str);
1498 return FALSE;
1502 /* end can be -1 for haystack->len.
1503 * returns: position of found text or -1. */
1504 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1506 gint pos;
1508 g_return_val_if_fail(haystack != NULL, -1);
1509 if (haystack->len == 0)
1510 return -1;
1512 g_return_val_if_fail(start >= 0, -1);
1513 if (start >= (gint)haystack->len)
1514 return -1;
1516 g_return_val_if_fail(!EMPTY(needle), -1);
1518 if (end < 0)
1519 end = haystack->len;
1521 pos = utils_strpos(haystack->str + start, needle);
1522 if (pos == -1)
1523 return -1;
1525 pos += start;
1526 if (pos >= end)
1527 return -1;
1528 return pos;
1532 /* Replaces @len characters from offset @a pos.
1533 * len can be -1 to replace the remainder of @a str.
1534 * returns: pos + strlen(replace). */
1535 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1537 g_string_erase(str, pos, len);
1538 if (replace)
1540 g_string_insert(str, pos, replace);
1541 pos += strlen(replace);
1543 return pos;
1548 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1549 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1550 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1552 * @param haystack The input string to operate on. This string is modified in place.
1553 * @param needle The string which should be replaced.
1554 * @param replace The replacement for @a needle.
1556 * @return Number of replacements made.
1558 GEANY_API_SYMBOL
1559 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1561 guint count = 0;
1562 gint pos = 0;
1563 gsize needle_length = strlen(needle);
1565 while (1)
1567 pos = utils_string_find(haystack, pos, -1, needle);
1569 if (pos == -1)
1570 break;
1572 pos = utils_string_replace(haystack, pos, needle_length, replace);
1573 count++;
1575 return count;
1580 * Replaces only the first occurrence of @a needle in @a haystack
1581 * with @a replace.
1582 * For details, see utils_string_replace_all().
1584 * @param haystack The input string to operate on. This string is modified in place.
1585 * @param needle The string which should be replaced.
1586 * @param replace The replacement for @a needle.
1588 * @return Number of replacements made.
1590 * @since 0.16
1592 GEANY_API_SYMBOL
1593 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1595 gint pos = utils_string_find(haystack, 0, -1, needle);
1597 if (pos == -1)
1598 return 0;
1600 utils_string_replace(haystack, pos, strlen(needle), replace);
1601 return 1;
1605 /* Similar to g_regex_replace but allows matching a subgroup.
1606 * match_num: which match to replace, 0 for whole match.
1607 * literal: FALSE to interpret escape sequences in @a replace.
1608 * returns: number of replacements.
1609 * bug: replaced text can affect matching of ^ or \b */
1610 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1611 guint match_num, const gchar *replace, gboolean literal)
1613 GMatchInfo *minfo;
1614 guint ret = 0;
1615 gint start = 0;
1617 g_assert(literal); /* escapes not implemented yet */
1618 g_return_val_if_fail(replace, 0);
1620 /* ensure haystack->str is not null */
1621 if (haystack->len == 0)
1622 return 0;
1624 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1625 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1627 gint end, len;
1629 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1630 len = end - start;
1631 utils_string_replace(haystack, start, len, replace);
1632 ret++;
1634 /* skip past whole match */
1635 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1636 start = end - len + strlen(replace);
1637 g_match_info_free(minfo);
1639 g_match_info_free(minfo);
1640 return ret;
1644 /* Get project or default startup directory (if set), or NULL. */
1645 const gchar *utils_get_default_dir_utf8(void)
1647 if (app->project && !EMPTY(app->project->base_path))
1649 return app->project->base_path;
1652 if (!EMPTY(prefs.default_open_path))
1654 return prefs.default_open_path;
1656 return NULL;
1661 * Wraps @c spawn_sync(), which see.
1663 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1664 * @param argv The child's argument vector.
1665 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1666 * @param flags Ignored.
1667 * @param child_setup @girskip Ignored.
1668 * @param user_data @girskip Ignored.
1669 * @param std_out @out @optional The return location for child output, or @c NULL.
1670 * @param std_err @out @optional The return location for child error messages, or @c NULL.
1671 * @param exit_status @out @optional The child exit status, as returned by waitpid(), or @c NULL.
1672 * @param error The return location for error or @c NULL.
1674 * @return @c TRUE on success, @c FALSE if an error was set.
1676 GEANY_API_SYMBOL
1677 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1678 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1679 gchar **std_err, gint *exit_status, GError **error)
1681 GString *output = std_out ? g_string_new(NULL) : NULL;
1682 GString *errors = std_err ? g_string_new(NULL) : NULL;
1683 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1685 if (std_out)
1686 *std_out = g_string_free(output, !result);
1688 if (std_err)
1689 *std_err = g_string_free(errors, !result);
1691 return result;
1696 * Wraps @c spawn_async(), which see.
1698 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1699 * @param argv The child's argument vector.
1700 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1701 * @param flags Ignored.
1702 * @param child_setup @girskip Ignored.
1703 * @param user_data Ignored.
1704 * @param child_pid @out @nullable The return location for child process ID, or @c NULL.
1705 * @param error The return location for error or @c NULL.
1707 * @return @c TRUE on success, @c FALSE if an error was set.
1709 GEANY_API_SYMBOL
1710 gboolean utils_spawn_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1711 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1712 GError **error)
1714 return spawn_async(dir, NULL, argv, env, child_pid, error);
1718 /* Returns "file:///" on Windows, "file://" everywhere else */
1719 const gchar *utils_get_uri_file_prefix(void)
1721 #ifdef G_OS_WIN32
1722 return "file:///";
1723 #else
1724 return "file://";
1725 #endif
1729 /* Retrieves the path for the given URI.
1730 * It returns:
1731 * - the path which was determined by g_filename_from_uri() or GIO
1732 * - NULL if the URI is non-local and gvfs-fuse is not installed
1733 * - a new copy of 'uri' if it is not an URI. */
1734 gchar *utils_get_path_from_uri(const gchar *uri)
1736 gchar *locale_filename;
1738 g_return_val_if_fail(uri != NULL, NULL);
1740 if (! utils_is_uri(uri))
1741 return g_strdup(uri);
1743 /* this will work only for 'file://' URIs */
1744 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1745 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1746 if (locale_filename == NULL)
1748 GFile *file = g_file_new_for_uri(uri);
1749 locale_filename = g_file_get_path(file);
1750 g_object_unref(file);
1751 if (locale_filename == NULL)
1753 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1754 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1758 return locale_filename;
1762 gboolean utils_is_uri(const gchar *uri)
1764 g_return_val_if_fail(uri != NULL, FALSE);
1766 return (strstr(uri, "://") != NULL);
1770 /* path should be in locale encoding */
1771 gboolean utils_is_remote_path(const gchar *path)
1773 g_return_val_if_fail(path != NULL, FALSE);
1775 /* if path is an URI and it doesn't start "file://", we take it as remote */
1776 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1777 return TRUE;
1779 #ifndef G_OS_WIN32
1781 static gchar *fuse_path = NULL;
1782 static gsize len = 0;
1784 if (G_UNLIKELY(fuse_path == NULL))
1786 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1787 len = strlen(fuse_path);
1789 /* Comparing the file path against a hardcoded path is not the most elegant solution
1790 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1791 * proper GFile objects for Fuse paths, but it only does in future GVFS
1792 * versions (gvfs 1.1.1). */
1793 return (strncmp(path, fuse_path, len) == 0);
1795 #endif
1797 return FALSE;
1801 /* Remove all relative and untidy elements from the path of @a filename.
1802 * @param filename must be a valid absolute path.
1803 * @see utils_get_real_path() - also resolves links. */
1804 void utils_tidy_path(gchar *filename)
1806 GString *str;
1807 const gchar *needle;
1808 gboolean preserve_double_backslash = FALSE;
1810 g_return_if_fail(g_path_is_absolute(filename));
1812 str = g_string_new(filename);
1814 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1815 preserve_double_backslash = TRUE;
1817 #ifdef G_OS_WIN32
1818 /* using MSYS we can get Unix-style separators */
1819 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1820 #endif
1821 /* replace "/./" and "//" */
1822 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1823 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1825 if (preserve_double_backslash)
1826 g_string_prepend(str, "\\");
1828 /* replace "/../" */
1829 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1830 while (1)
1832 const gchar *c = strstr(str->str, needle);
1833 if (c == NULL)
1834 break;
1835 else
1837 gssize pos, sub_len;
1839 pos = c - str->str;
1840 if (pos <= 3)
1841 break; /* bad path */
1843 /* replace "/../" */
1844 g_string_erase(str, pos, strlen(needle));
1845 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1847 /* search for last "/" before found "/../" */
1848 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1849 sub_len = pos - (c - str->str);
1850 if (! c)
1851 break; /* bad path */
1853 pos = c - str->str; /* position of previous "/" */
1854 g_string_erase(str, pos, sub_len);
1857 if (str->len <= strlen(filename))
1858 memcpy(filename, str->str, str->len + 1);
1859 else
1860 g_warn_if_reached();
1861 g_string_free(str, TRUE);
1866 * Removes characters from a string, in place.
1868 * @param string String to search.
1869 * @param chars Characters to remove.
1871 * @return @a string - return value is only useful when nesting function calls, e.g.:
1872 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1874 * @see @c g_strdelimit.
1876 GEANY_API_SYMBOL
1877 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1879 const gchar *r;
1880 gchar *w = string;
1882 g_return_val_if_fail(string, NULL);
1883 if (G_UNLIKELY(EMPTY(chars)))
1884 return string;
1886 foreach_str(r, string)
1888 if (!strchr(chars, *r))
1889 *w++ = *r;
1891 *w = 0x0;
1892 return string;
1896 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1897 GSList *utils_get_config_files(const gchar *subdir)
1899 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1900 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1901 GSList *syslist, *node;
1903 if (!list)
1905 utils_mkdir(path, FALSE);
1907 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1908 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1909 /* merge lists */
1910 list = g_slist_concat(list, syslist);
1912 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1913 /* remove duplicates (next to each other after sorting) */
1914 foreach_slist(node, list)
1916 if (node->next && utils_str_equal(node->next->data, node->data))
1918 GSList *old = node->next;
1920 g_free(old->data);
1921 node->next = old->next;
1922 g_slist_free1(old);
1925 g_free(path);
1926 return list;
1930 /* Suffix can be NULL or a string which should be appended to the Help URL like
1931 * an anchor link, e.g. "#some_anchor". */
1932 gchar *utils_get_help_url(const gchar *suffix)
1934 gchar *uri;
1935 const gchar *uri_file_prefix = utils_get_uri_file_prefix();
1936 gint skip = strlen(uri_file_prefix);
1938 uri = g_strconcat(uri_file_prefix, app->docdir, "/index.html", NULL);
1939 #ifdef G_OS_WIN32
1940 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1941 #endif
1943 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1944 { /* fall back to online documentation if it is not found on the hard disk */
1945 g_free(uri);
1946 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", PACKAGE_VERSION, "/index.html", NULL);
1949 if (suffix != NULL)
1951 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1954 return uri;
1958 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1960 const gchar **p;
1962 for (p = haystack; *p != NULL; ++p)
1964 if (utils_str_equal(*p, needle))
1965 return TRUE;
1967 return FALSE;
1972 * Copies the current environment into a new array.
1973 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1974 * All further arguments are key, value pairs of variables which should be added to
1975 * the environment.
1977 * The argument list must be @c NULL-terminated.
1979 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1980 * @param first_varname Name of the first variable to copy into the new array.
1981 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1983 * @return @transfer{full} The new environment array. Use @c g_strfreev() to free it.
1985 GEANY_API_SYMBOL
1986 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1988 gchar **result;
1989 gchar **p;
1990 gchar **env;
1991 va_list args;
1992 const gchar *key, *value;
1993 guint n, o;
1995 /* count the additional variables */
1996 va_start(args, first_varname);
1997 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1998 va_end(args);
1999 /* the passed arguments should be even (key, value pairs) */
2000 g_return_val_if_fail(o % 2 == 0, NULL);
2002 o /= 2;
2004 /* get all the environ variables */
2005 env = g_listenv();
2007 /* create an array large enough to hold the new environment */
2008 n = g_strv_length(env);
2009 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
2010 result = g_new(gchar *, n + o + 1);
2012 /* copy the environment */
2013 for (n = 0, p = env; *p != NULL; ++p)
2015 /* copy the variable */
2016 value = g_getenv(*p);
2017 if (G_LIKELY(value != NULL))
2019 /* skip excluded variables */
2020 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
2021 continue;
2023 result[n++] = g_strconcat(*p, "=", value, NULL);
2026 g_strfreev(env);
2028 /* now add additional variables */
2029 va_start(args, first_varname);
2030 key = first_varname;
2031 value = va_arg(args, gchar*);
2032 while (key != NULL)
2034 result[n++] = g_strconcat(key, "=", value, NULL);
2036 key = va_arg(args, gchar*);
2037 if (key == NULL)
2038 break;
2039 value = va_arg(args, gchar*);
2041 va_end(args);
2043 result[n] = NULL;
2045 return result;
2049 /* Joins @a first and @a second into a new string vector, freeing the originals.
2050 * The original contents are reused. */
2051 gchar **utils_strv_join(gchar **first, gchar **second)
2053 gchar **strv;
2054 gchar **rptr, **wptr;
2056 if (!first)
2057 return second;
2058 if (!second)
2059 return first;
2061 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2062 wptr = strv;
2064 foreach_strv(rptr, first)
2065 *wptr++ = *rptr;
2066 foreach_strv(rptr, second)
2067 *wptr++ = *rptr;
2069 g_free(first);
2070 g_free(second);
2071 return strv;
2074 /* * Returns the common prefix in a list of strings.
2076 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2078 * @param strv The list of strings to process.
2079 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2081 * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list
2082 * was passed in.
2084 GEANY_EXPORT_SYMBOL
2085 gchar *utils_strv_find_common_prefix(gchar **strv, gssize strv_len)
2087 gsize num;
2089 if (strv_len == 0)
2090 return NULL;
2092 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2094 for (gsize i = 0; strv[0][i]; i++)
2096 for (gsize j = 1; j < num; j++)
2098 if (strv[j][i] != strv[0][i])
2100 /* return prefix on first mismatch */
2101 return g_strndup(strv[0], i);
2106 return g_strdup(strv[0]);
2110 /* * Returns the longest common substring in a list of strings.
2112 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2114 * @param strv The list of strings to process.
2115 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2117 * @return The common prefix that is part of all strings.
2119 GEANY_EXPORT_SYMBOL
2120 gchar *utils_strv_find_lcs(gchar **strv, gssize strv_len, const gchar *delim)
2122 gchar *first, *_sub, *sub;
2123 gsize num;
2124 gsize n_chars;
2125 gsize len;
2126 gsize max = 0;
2127 char *lcs;
2128 gsize found;
2130 if (strv_len == 0)
2131 return NULL;
2133 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2135 first = strv[0];
2136 len = strlen(first);
2138 /* sub is the working area where substrings from first are copied to */
2139 sub = g_malloc(len+1);
2140 lcs = g_strdup("");
2141 foreach_str(_sub, first)
2143 gsize chars_left = len - (_sub - first);
2144 /* No point in continuing if the remainder is too short */
2145 if (max > chars_left)
2146 break;
2147 /* If delimiters are given, we only need to compare substrings which start and
2148 * end with one of them, so skip any non-delim chars at front ... */
2149 if (NZV(delim) && (strchr(delim, _sub[0]) == NULL))
2150 continue;
2151 for (n_chars = 1; n_chars <= chars_left; n_chars++)
2153 if (NZV(delim))
2154 { /* ... and advance to the next delim char at the end, if any */
2155 if (!_sub[n_chars] || strchr(delim, _sub[n_chars]) == NULL)
2156 continue;
2157 n_chars += 1;
2159 g_strlcpy(sub, _sub, n_chars+1);
2160 found = 1;
2161 for (gsize i = 1; i < num; i++)
2163 if (strstr(strv[i], sub) == NULL)
2164 break;
2165 found++;
2167 if (found == num && n_chars > max)
2169 max = n_chars;
2170 SETPTR(lcs, g_strdup(sub));
2174 g_free(sub);
2176 return lcs;
2180 /** Transform file names in a list to be shorter.
2182 * This function takes a list of file names (probably with absolute paths), and
2183 * transforms the paths such that they are short but still unique. This is intended
2184 * for dialogs which present the file list to the user, where the base name may result
2185 * in duplicates (showing the full path might be inappropriate).
2187 * The algorthm strips the common prefix (e-g. the user's home directory) and
2188 * replaces the longest common substring with an ellipsis ("...").
2190 * @param file_names @array{length=file_names_len} The list of strings to process.
2191 * @param file_names_len The number of strings contained in @a file_names. Can be -1 if it's
2192 * terminated by @c NULL.
2193 * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by
2194 @c NULL. Use @c g_strfreev() to free it.
2196 * @since 1.34 (API 239)
2198 GEANY_API_SYMBOL
2199 gchar **utils_strv_shorten_file_list(gchar **file_names, gssize file_names_len)
2201 gsize num;
2202 gsize i;
2203 gchar *prefix, *lcs, *end;
2204 gchar **names;
2205 gsize prefix_len = 0, lcs_len = 0;
2207 if (file_names_len == 0)
2208 return g_new0(gchar *, 1);
2210 g_return_val_if_fail(file_names != NULL, NULL);
2212 num = (file_names_len == -1) ? g_strv_length(file_names) : (gsize) file_names_len;
2213 /* Always include a terminating NULL, enables easy freeing with g_strfreev()
2214 * We just copy the pointers so we can advance them here. But don't
2215 * forget to duplicate the strings before returning.
2217 names = g_new(gchar *, num + 1);
2218 memcpy(names, file_names, num * sizeof(gchar *));
2219 /* Always include a terminating NULL, enables easy freeing with g_strfreev() */
2220 names[num] = NULL;
2222 /* First: determine the common prefix, that will be stripped.
2223 * We only want to strip full path components, including the trailing slash.
2224 * Except if the component is just "/".
2226 prefix = utils_strv_find_common_prefix(names, num);
2227 end = strrchr(prefix, G_DIR_SEPARATOR);
2228 if (end && end > prefix)
2230 prefix_len = end - prefix + 1; /* prefix_len includes the trailing slash */
2231 for (i = 0; i < num; i++)
2232 names[i] += prefix_len;
2235 /* Second: determine the longest common substring (lcs), that will be ellipsized. Again,
2236 * we look only for full path compnents so that we ellipsize between separators. This implies
2237 * that the file name cannot be ellipsized which is desirable anyway.
2239 lcs = utils_strv_find_lcs(names, num, G_DIR_SEPARATOR_S"/");
2240 if (lcs)
2242 lcs_len = strlen(lcs);
2243 /* Don't bother for tiny common parts (which are often just "." or "/"). Beware
2244 * that lcs includes the enclosing dir separators so the part must be at least 5 chars
2245 * to be eligible for ellipsizing.
2247 if (lcs_len < 7)
2248 lcs_len = 0;
2251 /* Last: build the shortened list of unique file names */
2252 for (i = 0; i < num; i++)
2254 if (lcs_len == 0)
2255 { /* no lcs, copy without prefix */
2256 names[i] = g_strdup(names[i]);
2258 else
2260 const gchar *lcs_start = strstr(names[i], lcs);
2261 const gchar *lcs_end = lcs_start + lcs_len;
2262 /* Dir seperators are included in lcs but shouldn't be elipsized. */
2263 names[i] = g_strdup_printf("%.*s...%s", (int)(lcs_start - names[i] + 1), names[i], lcs_end - 1);
2267 g_free(lcs);
2268 g_free(prefix);
2270 return names;
2274 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2275 * obviously g_date_set_parse() uses some magic.
2276 * The returned GDate object must be freed. */
2277 GDate *utils_parse_date(const gchar *input)
2279 GDate *date = g_date_new();
2281 g_date_set_parse(date, input);
2283 if (g_date_valid(date))
2284 return date;
2286 g_date_free(date);
2287 return NULL;
2291 gchar *utils_parse_and_format_build_date(const gchar *input)
2293 gchar date_buf[255];
2294 GDate *date = utils_parse_date(input);
2296 if (date != NULL)
2298 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2299 g_date_free(date);
2300 return g_strdup(date_buf);
2303 return g_strdup(input);
2307 gchar *utils_get_user_config_dir(void)
2309 #ifdef G_OS_WIN32
2310 return win32_get_user_config_dir();
2311 #else
2312 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2313 #endif
2317 static gboolean is_osx_bundle(void)
2319 #ifdef MAC_INTEGRATION
2320 gchar *bundle_id = gtkosx_application_get_bundle_id();
2321 if (bundle_id)
2323 g_free(bundle_id);
2324 return TRUE;
2326 #endif
2327 return FALSE;
2331 const gchar *utils_resource_dir(GeanyResourceDirType type)
2333 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2335 if (!resdirs[RESOURCE_DIR_DATA])
2337 #ifdef G_OS_WIN32
2338 gchar *prefix = win32_get_installation_dir();
2340 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", 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 #else
2348 if (is_osx_bundle())
2350 # ifdef MAC_INTEGRATION
2351 gchar *prefix = gtkosx_application_get_resource_path();
2353 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2354 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2355 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2356 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2357 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2358 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2359 g_free(prefix);
2360 # endif
2362 else
2364 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2365 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2366 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2367 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2368 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2369 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(GEANY_LIBEXECDIR, "geany", NULL);
2371 #endif
2374 return resdirs[type];
2378 void utils_start_new_geany_instance(const gchar *doc_path)
2380 const gchar *command = is_osx_bundle() ? "open" : "geany";
2381 gchar *exec_path = g_find_program_in_path(command);
2383 if (exec_path)
2385 GError *err = NULL;
2386 const gchar *argv[6]; // max args + 1
2387 gint argc = 0;
2389 argv[argc++] = exec_path;
2390 if (is_osx_bundle())
2392 argv[argc++] = "-n";
2393 argv[argc++] = "-a";
2394 argv[argc++] = "Geany";
2395 argv[argc++] = doc_path;
2397 else
2399 argv[argc++] = "-i";
2400 argv[argc++] = doc_path;
2402 argv[argc] = NULL;
2404 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2406 g_printerr("Unable to open new window: %s\n", err->message);
2407 g_error_free(err);
2409 g_free(exec_path);
2411 else
2412 g_printerr("Unable to find 'geany'\n");
2417 * Get a link-dereferenced, absolute version of a file name.
2419 * This is similar to the POSIX `realpath` function when passed a
2420 * @c NULL argument.
2422 * @warning This function suffers the same problems as the POSIX
2423 * function `realpath()`, namely that it's impossible to determine
2424 * a suitable size for the returned buffer, and so it's limited to a
2425 * maximum of `PATH_MAX`.
2427 * @param file_name The file name to get the real path of.
2429 * @return A newly-allocated string containing the real path which
2430 * should be freed with `g_free()` when no longer needed, or @c NULL
2431 * if the real path cannot be obtained.
2433 * @since 1.32 (API 235)
2435 GEANY_API_SYMBOL
2436 gchar *utils_get_real_path(const gchar *file_name)
2438 return tm_get_real_path(file_name);
2443 * Get a string describing the OS.
2445 * If the OS can be determined, a string which describes the OS will
2446 * be returned. If no OS can be determined then `NULL` will be returned.
2448 * @note The format of the returned string is unspecified and is only
2449 * meant to provide diagnostic information to the user.
2451 * @return A newly-allocated string containing a description of the
2452 * OS if it can be determined or `NULL` if it cannot.
2454 * @since 1.37
2456 gchar *utils_get_os_info_string(void)
2458 gchar *os_info = NULL;
2460 #if GLIB_CHECK_VERSION(2, 64, 0)
2461 # if ! defined(__APPLE__)
2462 /* on non-macOS operating systems */
2464 GString *os_str;
2465 gchar *pretty_name;
2466 gchar *code_name;
2468 pretty_name = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
2469 if (pretty_name == NULL)
2470 return NULL;
2472 os_str = g_string_new(pretty_name);
2473 g_free(pretty_name);
2475 code_name = g_get_os_info(G_OS_INFO_KEY_VERSION_CODENAME);
2476 if (code_name != NULL)
2478 g_string_append_printf(os_str, " (%s)", code_name);
2479 g_free(code_name);
2482 os_info = g_string_free(os_str, FALSE);
2484 # else
2485 /* on macOS, only `G_OS_INFO_KEY_NAME` is supported and returns the
2486 * fixed string `macOS` */
2487 os_info = g_get_os_info(G_OS_INFO_KEY_NAME);
2488 # endif
2489 #else
2490 /* if g_get_os_info() is not available, do it the old-fashioned way */
2491 # if defined(_WIN64)
2492 os_info = g_strdup("Microsoft Windows (64-bit)");
2493 # elif defined(_WIN32)
2494 os_info = g_strdup("Microsoft Windows");
2495 # elif defined(__APPLE__)
2496 os_info = g_strdup("Apple macOS");
2497 # elif defined(__linux__)
2498 os_info = g_strdup("Linux");
2499 # elif defined(__FreeBSD__)
2500 os_info = g_strdup("FreeBSD");
2501 # elif defined(__ANDROID__)
2502 os_info = g_strdup("Android");
2503 # endif
2504 #endif
2506 return os_info;