Merge pull request #3948 from techee/warning_fix2
[geany-mirror.git] / src / utils.c
blob1d3e377c808d8ac3d0550da4684eee1d390a4dd9
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 /* Extracts initials from @p name, with basic Unicode support */
770 GEANY_EXPORT_SYMBOL
771 gchar *utils_get_initials(const gchar *name)
773 GString *initials;
774 gchar *composed;
775 gboolean at_bound = TRUE;
777 g_return_val_if_fail(name != NULL, NULL);
779 composed = g_utf8_normalize(name, -1, G_NORMALIZE_ALL_COMPOSE);
780 g_return_val_if_fail(composed != NULL, NULL);
782 initials = g_string_new(NULL);
783 for (const gchar *p = composed; *p; p = g_utf8_next_char(p))
785 gunichar ch = g_utf8_get_char(p);
787 if (g_unichar_isspace(ch))
788 at_bound = TRUE;
789 else if (at_bound)
791 const gchar *end = g_utf8_next_char(p);
792 g_string_append_len(initials, p, end - p);
793 at_bound = FALSE;
797 g_free(composed);
799 return g_string_free(initials, FALSE);
804 * Wraps g_key_file_get_integer() to add a default value argument.
806 * @param config A GKeyFile object.
807 * @param section The group name to look in for the key.
808 * @param key The key to find.
809 * @param default_value The default value which will be returned when @a section or @a key
810 * don't exist.
812 * @return The value associated with @a key as an integer, or the given default value if the value
813 * could not be retrieved.
815 GEANY_API_SYMBOL
816 gint utils_get_setting_integer(GKeyFile *config, const gchar *section, const gchar *key,
817 const gint default_value)
819 gint tmp;
820 GError *error = NULL;
822 g_return_val_if_fail(config, default_value);
824 tmp = g_key_file_get_integer(config, section, key, &error);
825 if (error)
827 g_error_free(error);
828 return default_value;
830 return tmp;
835 * Wraps g_key_file_get_boolean() to add a default value argument.
837 * @param config A GKeyFile object.
838 * @param section The group name to look in for the key.
839 * @param key The key to find.
840 * @param default_value The default value which will be returned when @c section or @c key
841 * don't exist.
843 * @return The value associated with @a key as a boolean, or the given default value if the value
844 * could not be retrieved.
846 GEANY_API_SYMBOL
847 gboolean utils_get_setting_boolean(GKeyFile *config, const gchar *section, const gchar *key,
848 const gboolean default_value)
850 gboolean tmp;
851 GError *error = NULL;
853 g_return_val_if_fail(config, default_value);
855 tmp = g_key_file_get_boolean(config, section, key, &error);
856 if (error)
858 g_error_free(error);
859 return default_value;
861 return tmp;
866 * Wraps g_key_file_get_double() to add a default value argument.
868 * @param config A GKeyFile object.
869 * @param section The group name to look in for the key.
870 * @param key The key to find.
871 * @param default_value The default value which will be returned when @a section or @a key
872 * don't exist.
874 * @return The value associated with @a key as an integer, or the given default value if the value
875 * could not be retrieved.
877 GEANY_API_SYMBOL
878 gdouble utils_get_setting_double(GKeyFile *config, const gchar *section, const gchar *key,
879 const gdouble default_value)
881 gdouble tmp;
882 GError *error = NULL;
884 g_return_val_if_fail(config, default_value);
886 tmp = g_key_file_get_double(config, section, key, &error);
887 if (error)
889 g_error_free(error);
890 return default_value;
892 return tmp;
897 * Wraps g_key_file_get_string() to add a default value argument.
899 * @param config A GKeyFile object.
900 * @param section The group name to look in for the key.
901 * @param key The key to find.
902 * @param default_value The default value which will be returned when @a section or @a key
903 * don't exist.
905 * @return A newly allocated string, either the value for @a key or a copy of the given
906 * default value if it could not be retrieved.
908 GEANY_API_SYMBOL
909 gchar *utils_get_setting_string(GKeyFile *config, const gchar *section, const gchar *key,
910 const gchar *default_value)
912 gchar *tmp;
914 g_return_val_if_fail(config, g_strdup(default_value));
916 tmp = g_key_file_get_string(config, section, key, NULL);
917 if (!tmp)
919 return g_strdup(default_value);
921 return tmp;
925 gchar *utils_get_hex_from_color(GdkColor *color)
927 g_return_val_if_fail(color != NULL, NULL);
929 return g_strdup_printf("#%02X%02X%02X",
930 (guint) (utils_scale_round(color->red / 65535.0, 255)),
931 (guint) (utils_scale_round(color->green / 65535.0, 255)),
932 (guint) (utils_scale_round(color->blue / 65535.0, 255)));
936 /* Get directory from current file in the notebook.
937 * Returns dir string that should be freed or NULL, depending on whether current file is valid.
938 * Returned string is in UTF-8 encoding */
939 gchar *utils_get_current_file_dir_utf8(void)
941 GeanyDocument *doc = document_get_current();
943 if (doc != NULL)
945 /* get current filename */
946 const gchar *cur_fname = doc->file_name;
948 if (cur_fname != NULL)
950 /* get folder part from current filename */
951 return g_path_get_dirname(cur_fname); /* returns "." if no path */
955 return NULL; /* no file open */
959 /* very simple convenience function */
960 void utils_beep(void)
962 if (prefs.beep_on_errors)
963 gdk_beep();
967 /* converts a color representation using gdk_color_parse(), with additional
968 * support of the "0x" prefix as a synonym for "#" */
969 gboolean utils_parse_color(const gchar *spec, GdkColor *color)
971 gchar buf[64] = {0};
973 g_return_val_if_fail(spec != NULL, -1);
975 if (spec[0] == '0' && (spec[1] == 'x' || spec[1] == 'X'))
977 /* convert to # format for GDK to understand it */
978 buf[0] = '#';
979 strncpy(buf + 1, spec + 2, sizeof(buf) - 2);
980 spec = buf;
983 return gdk_color_parse(spec, color);
987 /* converts a GdkColor to the packed 24 bits BGR format, as understood by Scintilla
988 * returns a 24 bits BGR color, or -1 on failure */
989 gint utils_color_to_bgr(const GdkColor *c)
991 g_return_val_if_fail(c != NULL, -1);
992 return (c->red / 256) | ((c->green / 256) << 8) | ((c->blue / 256) << 16);
996 /* parses @p spec using utils_parse_color() and convert it to 24 bits BGR using
997 * utils_color_to_bgr() */
998 gint utils_parse_color_to_bgr(const gchar *spec)
1000 GdkColor color;
1001 if (utils_parse_color(spec, &color))
1002 return utils_color_to_bgr(&color);
1003 else
1004 return -1;
1008 /* Returns: newly allocated string with the current time formatted HH:MM:SS.
1009 * If "include_microseconds" is TRUE, microseconds are appended.
1011 * The returned string should be freed with g_free(). */
1012 gchar *utils_get_current_time_string(gboolean include_microseconds)
1014 // "%f" specifier for microseconds is only available since GLib 2.66
1015 if (glib_check_version(2, 66, 0) != NULL)
1016 include_microseconds = FALSE;
1018 GDateTime *now = g_date_time_new_now_local();
1019 const gchar *format = include_microseconds ? "%H:%M:%S.%f" : "%H:%M:%S";
1020 gchar *time_string = g_date_time_format(now, format);
1021 g_date_time_unref(now);
1022 return time_string;
1026 GIOChannel *utils_set_up_io_channel(
1027 gint fd, GIOCondition cond, gboolean nblock, GIOFunc func, gpointer data)
1029 GIOChannel *ioc;
1030 /*const gchar *encoding;*/
1032 #ifdef G_OS_WIN32
1033 ioc = g_io_channel_win32_new_fd(fd);
1034 #else
1035 ioc = g_io_channel_unix_new(fd);
1036 #endif
1038 if (nblock)
1039 g_io_channel_set_flags(ioc, G_IO_FLAG_NONBLOCK, NULL);
1041 g_io_channel_set_encoding(ioc, NULL, NULL);
1043 if (! g_get_charset(&encoding))
1044 { // hope this works reliably
1045 GError *error = NULL;
1046 g_io_channel_set_encoding(ioc, encoding, &error);
1047 if (error)
1049 geany_debug("%s: %s", G_STRFUNC, error->message);
1050 g_error_free(error);
1051 return ioc;
1055 /* "auto-close" ;-) */
1056 g_io_channel_set_close_on_unref(ioc, TRUE);
1058 g_io_add_watch(ioc, cond, func, data);
1059 g_io_channel_unref(ioc);
1061 return ioc;
1065 /* Contributed by Stefan Oltmanns, thanks.
1066 * Replaces \\, \r, \n, \t and \uXXX by their real counterparts.
1067 * keep_backslash is used for regex strings to leave '\\' and '\?' in place */
1068 gboolean utils_str_replace_escape(gchar *string, gboolean keep_backslash)
1070 gsize i, j, len;
1071 guint unicodechar;
1073 g_return_val_if_fail(string != NULL, FALSE);
1075 j = 0;
1076 len = strlen(string);
1077 for (i = 0; i < len; i++)
1079 if (string[i]=='\\')
1081 if (i++ >= strlen(string))
1083 return FALSE;
1085 switch (string[i])
1087 case '\\':
1088 if (keep_backslash)
1089 string[j++] = '\\';
1090 string[j] = '\\';
1091 break;
1092 case 'n':
1093 string[j] = '\n';
1094 break;
1095 case 'r':
1096 string[j] = '\r';
1097 break;
1098 case 't':
1099 string[j] = '\t';
1100 break;
1101 #if 0
1102 case 'x': /* Warning: May produce illegal utf-8 string! */
1103 i += 2;
1104 if (i >= strlen(string))
1106 return FALSE;
1108 if (isdigit(string[i - 1])) string[j] = string[i - 1] - 48;
1109 else if (isxdigit(string[i - 1])) string[j] = tolower(string[i - 1])-87;
1110 else return FALSE;
1111 string[j] <<= 4;
1112 if (isdigit(string[i])) string[j] |= string[i] - 48;
1113 else if (isxdigit(string[i])) string[j] |= tolower(string[i])-87;
1114 else return FALSE;
1115 break;
1116 #endif
1117 case 'u':
1119 i += 2;
1120 if (i >= strlen(string))
1122 return FALSE;
1124 if (isdigit(string[i - 1])) unicodechar = string[i - 1] - 48;
1125 else if (isxdigit(string[i - 1])) unicodechar = tolower(string[i - 1])-87;
1126 else return FALSE;
1127 unicodechar <<= 4;
1128 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1129 else if (isxdigit(string[i])) unicodechar |= tolower(string[i])-87;
1130 else return FALSE;
1131 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1132 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1134 i += 2;
1135 unicodechar <<= 8;
1136 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1137 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1138 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1139 else unicodechar |= tolower(string[i])-87;
1141 if (((i + 2) < strlen(string)) && (isdigit(string[i + 1]) || isxdigit(string[i + 1]))
1142 && (isdigit(string[i + 2]) || isxdigit(string[i + 2])))
1144 i += 2;
1145 unicodechar <<= 8;
1146 if (isdigit(string[i - 1])) unicodechar |= ((string[i - 1] - 48) << 4);
1147 else unicodechar |= ((tolower(string[i - 1])-87) << 4);
1148 if (isdigit(string[i])) unicodechar |= string[i] - 48;
1149 else unicodechar |= tolower(string[i])-87;
1151 if (unicodechar < 0x80)
1153 string[j] = unicodechar;
1155 else if (unicodechar < 0x800)
1157 string[j] = (unsigned char) ((unicodechar >> 6) | 0xC0);
1158 j++;
1159 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1161 else if (unicodechar < 0x10000)
1163 string[j] = (unsigned char) ((unicodechar >> 12) | 0xE0);
1164 j++;
1165 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1166 j++;
1167 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1169 else if (unicodechar < 0x110000) /* more chars are not allowed in unicode */
1171 string[j] = (unsigned char) ((unicodechar >> 18) | 0xF0);
1172 j++;
1173 string[j] = (unsigned char) (((unicodechar >> 12) & 0x3F) | 0x80);
1174 j++;
1175 string[j] = (unsigned char) (((unicodechar >> 6) & 0x3F) | 0x80);
1176 j++;
1177 string[j] = (unsigned char) ((unicodechar & 0x3F) | 0x80);
1179 else
1181 return FALSE;
1183 break;
1185 default:
1186 /* unnecessary escapes are allowed */
1187 if (keep_backslash)
1188 string[j++] = '\\';
1189 string[j] = string[i];
1192 else
1194 string[j] = string[i];
1196 j++;
1198 while (j < i)
1200 string[j] = 0;
1201 j++;
1203 return TRUE;
1207 /* Wraps a string in place, replacing a space with a newline character.
1208 * wrapstart is the minimum position to start wrapping or -1 for default */
1209 gboolean utils_wrap_string(gchar *string, gint wrapstart)
1211 gchar *pos, *linestart;
1212 gboolean ret = FALSE;
1214 if (wrapstart < 0)
1215 wrapstart = 80;
1217 for (pos = linestart = string; *pos != '\0'; pos++)
1219 if (pos - linestart >= wrapstart && *pos == ' ')
1221 *pos = '\n';
1222 linestart = pos;
1223 ret = TRUE;
1226 return ret;
1231 * Converts the given UTF-8 encoded string into locale encoding.
1232 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1234 * @param utf8_text UTF-8 encoded text.
1236 * @return The converted string in locale encoding, or a copy of the input string if conversion
1237 * failed. Should be freed with g_free(). If @a utf8_text is @c NULL, @c NULL is returned.
1239 GEANY_API_SYMBOL
1240 gchar *utils_get_locale_from_utf8(const gchar *utf8_text)
1242 #ifdef G_OS_WIN32
1243 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1244 * which would result in wrongly converted strings */
1245 return g_strdup(utf8_text);
1246 #else
1247 gchar *locale_text;
1249 if (! utf8_text)
1250 return NULL;
1251 locale_text = g_locale_from_utf8(utf8_text, -1, NULL, NULL, NULL);
1252 if (locale_text == NULL)
1253 locale_text = g_strdup(utf8_text);
1254 return locale_text;
1255 #endif
1260 * Converts the given string (in locale encoding) into UTF-8 encoding.
1261 * On Windows platforms, it does nothing and instead it just returns a copy of the input string.
1263 * @param locale_text Text in locale encoding.
1265 * @return The converted string in UTF-8 encoding, or a copy of the input string if conversion
1266 * failed. Should be freed with g_free(). If @a locale_text is @c NULL, @c NULL is returned.
1268 GEANY_API_SYMBOL
1269 gchar *utils_get_utf8_from_locale(const gchar *locale_text)
1271 #ifdef G_OS_WIN32
1272 /* just do nothing on Windows platforms, this ifdef is just to prevent unwanted conversions
1273 * which would result in wrongly converted strings */
1274 return g_strdup(locale_text);
1275 #else
1276 gchar *utf8_text;
1278 if (! locale_text)
1279 return NULL;
1280 utf8_text = g_locale_to_utf8(locale_text, -1, NULL, NULL, NULL);
1281 if (utf8_text == NULL)
1282 utf8_text = g_strdup(locale_text);
1283 return utf8_text;
1284 #endif
1288 /* Pass pointers to free after arg_count.
1289 * The last argument must be NULL as an extra check that arg_count is correct. */
1290 void utils_free_pointers(gsize arg_count, ...)
1292 va_list a;
1293 gsize i;
1294 gpointer ptr;
1296 va_start(a, arg_count);
1297 for (i = 0; i < arg_count; i++)
1299 ptr = va_arg(a, gpointer);
1300 g_free(ptr);
1302 ptr = va_arg(a, gpointer);
1303 if (ptr)
1304 g_warning("Wrong arg_count!");
1305 va_end(a);
1309 /* Creates a string array deep copy of a series of non-NULL strings.
1310 * The first argument is nothing special and must not be NULL.
1311 * The list must be terminated with NULL. */
1312 GEANY_EXPORT_SYMBOL
1313 gchar **utils_strv_new(const gchar *first, ...)
1315 gsize strvlen, i;
1316 va_list args;
1317 gchar *str;
1318 gchar **strv;
1320 g_return_val_if_fail(first != NULL, NULL);
1322 strvlen = 1; /* for first argument */
1324 /* count other arguments */
1325 va_start(args, first);
1326 for (; va_arg(args, gchar*) != NULL; strvlen++);
1327 va_end(args);
1329 strv = g_new(gchar*, strvlen + 1); /* +1 for NULL terminator */
1330 strv[0] = g_strdup(first);
1332 va_start(args, first);
1333 for (i = 1; str = va_arg(args, gchar*), str != NULL; i++)
1335 strv[i] = g_strdup(str);
1337 va_end(args);
1339 strv[i] = NULL;
1340 return strv;
1345 * Creates a directory if it doesn't already exist.
1346 * Creates intermediate parent directories as needed, too.
1347 * The permissions of the created directory are set 0700.
1349 * @param path The path of the directory to create, in locale encoding.
1350 * @param create_parent_dirs Whether to create intermediate parent directories if necessary.
1352 * @return 0 if the directory was successfully created, otherwise the @c errno of the
1353 * failed operation is returned.
1355 GEANY_API_SYMBOL
1356 gint utils_mkdir(const gchar *path, gboolean create_parent_dirs)
1358 gint mode = 0700;
1359 gint result;
1361 if (path == NULL || strlen(path) == 0)
1362 return EFAULT;
1364 result = (create_parent_dirs) ? g_mkdir_with_parents(path, mode) : g_mkdir(path, mode);
1365 if (result != 0)
1366 return errno;
1367 return 0;
1372 * Gets a list of files from the specified directory.
1373 * Locale encoding is expected for @a path and used for the file list. The list and the data
1374 * in the list should be freed after use, e.g.:
1375 * @code
1376 * g_slist_foreach(list, (GFunc) g_free, NULL);
1377 * g_slist_free(list); @endcode
1379 * @note If you don't need a list you should use the foreach_dir() macro instead -
1380 * it's more efficient.
1382 * @param path The path of the directory to scan, in locale encoding.
1383 * @param full_path Whether to include the full path for each filename in the list. Obviously this
1384 * will use more memory.
1385 * @param sort Whether to sort alphabetically (UTF-8 safe).
1386 * @param error The location for storing a possible error, or @c NULL.
1388 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL if
1389 * no files were found. The list and its data should be freed when no longer needed.
1390 * @see utils_get_file_list().
1392 GEANY_API_SYMBOL
1393 GSList *utils_get_file_list_full(const gchar *path, gboolean full_path, gboolean sort, GError **error)
1395 GSList *list = NULL;
1396 GDir *dir;
1397 const gchar *filename;
1399 if (error)
1400 *error = NULL;
1401 g_return_val_if_fail(path != NULL, NULL);
1403 dir = g_dir_open(path, 0, error);
1404 if (dir == NULL)
1405 return NULL;
1407 foreach_dir(filename, dir)
1409 list = g_slist_prepend(list, full_path ?
1410 g_build_path(G_DIR_SEPARATOR_S, path, filename, NULL) : g_strdup(filename));
1412 g_dir_close(dir);
1413 /* sorting last is quicker than on insertion */
1414 if (sort)
1415 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1416 return list;
1421 * Gets a sorted list of files from the specified directory.
1422 * Locale encoding is expected for @a path and used for the file list. The list and the data
1423 * in the list should be freed after use, e.g.:
1424 * @code
1425 * g_slist_foreach(list, (GFunc) g_free, NULL);
1426 * g_slist_free(list); @endcode
1428 * @param path The path of the directory to scan, in locale encoding.
1429 * @param length The location to store the number of non-@c NULL data items in the list,
1430 * unless @c NULL.
1431 * @param error The location for storing a possible error, or @c NULL.
1433 * @return @elementtype{filename} @transfer{full} @nullable A newly allocated list or @c NULL
1434 * if no files were found. The list and its data should be freed when no longer needed.
1435 * @see utils_get_file_list_full().
1437 GEANY_API_SYMBOL
1438 GSList *utils_get_file_list(const gchar *path, guint *length, GError **error)
1440 GSList *list = utils_get_file_list_full(path, FALSE, TRUE, error);
1442 if (length)
1443 *length = g_slist_length(list);
1444 return list;
1448 /* returns TRUE if any letter in str is a capital, FALSE otherwise. Should be Unicode safe. */
1449 gboolean utils_str_has_upper(const gchar *str)
1451 gunichar c;
1453 if (EMPTY(str) || ! g_utf8_validate(str, -1, NULL))
1454 return FALSE;
1456 while (*str != '\0')
1458 c = g_utf8_get_char(str);
1459 /* check only letters and stop once the first non-capital was found */
1460 if (g_unichar_isalpha(c) && g_unichar_isupper(c))
1461 return TRUE;
1462 /* FIXME don't write a const string */
1463 str = g_utf8_next_char(str);
1465 return FALSE;
1469 /* end can be -1 for haystack->len.
1470 * returns: position of found text or -1. */
1471 gint utils_string_find(GString *haystack, gint start, gint end, const gchar *needle)
1473 gint pos;
1475 g_return_val_if_fail(haystack != NULL, -1);
1476 if (haystack->len == 0)
1477 return -1;
1479 g_return_val_if_fail(start >= 0, -1);
1480 if (start >= (gint)haystack->len)
1481 return -1;
1483 g_return_val_if_fail(!EMPTY(needle), -1);
1485 if (end < 0)
1486 end = haystack->len;
1488 pos = utils_strpos(haystack->str + start, needle);
1489 if (pos == -1)
1490 return -1;
1492 pos += start;
1493 if (pos >= end)
1494 return -1;
1495 return pos;
1499 /* Replaces @len characters from offset @a pos.
1500 * len can be -1 to replace the remainder of @a str.
1501 * returns: pos + strlen(replace). */
1502 gint utils_string_replace(GString *str, gint pos, gint len, const gchar *replace)
1504 g_string_erase(str, pos, len);
1505 if (replace)
1507 g_string_insert(str, pos, replace);
1508 pos += strlen(replace);
1510 return pos;
1515 * Replaces all occurrences of @a needle in @a haystack with @a replace.
1516 * As of Geany 0.16, @a replace can match @a needle, so the following will work:
1517 * @code utils_string_replace_all(text, "\n", "\r\n"); @endcode
1519 * @param haystack The input string to operate on. This string is modified in place.
1520 * @param needle The string which should be replaced.
1521 * @param replace The replacement for @a needle.
1523 * @return Number of replacements made.
1525 GEANY_API_SYMBOL
1526 guint utils_string_replace_all(GString *haystack, const gchar *needle, const gchar *replace)
1528 guint count = 0;
1529 gint pos = 0;
1530 gsize needle_length = strlen(needle);
1532 while (1)
1534 pos = utils_string_find(haystack, pos, -1, needle);
1536 if (pos == -1)
1537 break;
1539 pos = utils_string_replace(haystack, pos, needle_length, replace);
1540 count++;
1542 return count;
1547 * Replaces only the first occurrence of @a needle in @a haystack
1548 * with @a replace.
1549 * For details, see utils_string_replace_all().
1551 * @param haystack The input string to operate on. This string is modified in place.
1552 * @param needle The string which should be replaced.
1553 * @param replace The replacement for @a needle.
1555 * @return Number of replacements made.
1557 * @since 0.16
1559 GEANY_API_SYMBOL
1560 guint utils_string_replace_first(GString *haystack, const gchar *needle, const gchar *replace)
1562 gint pos = utils_string_find(haystack, 0, -1, needle);
1564 if (pos == -1)
1565 return 0;
1567 utils_string_replace(haystack, pos, strlen(needle), replace);
1568 return 1;
1572 /* Similar to g_regex_replace but allows matching a subgroup.
1573 * match_num: which match to replace, 0 for whole match.
1574 * literal: FALSE to interpret escape sequences in @a replace.
1575 * returns: number of replacements.
1576 * bug: replaced text can affect matching of ^ or \b */
1577 guint utils_string_regex_replace_all(GString *haystack, GRegex *regex,
1578 guint match_num, const gchar *replace, gboolean literal)
1580 GMatchInfo *minfo;
1581 guint ret = 0;
1582 gint start = 0;
1584 g_assert(literal); /* escapes not implemented yet */
1585 g_return_val_if_fail(replace, 0);
1587 /* ensure haystack->str is not null */
1588 if (haystack->len == 0)
1589 return 0;
1591 /* passing a start position makes G_REGEX_MATCH_NOTBOL automatic */
1592 while (g_regex_match_full(regex, haystack->str, -1, start, 0, &minfo, NULL))
1594 gint end, len;
1596 g_match_info_fetch_pos(minfo, match_num, &start, &end);
1597 len = end - start;
1598 utils_string_replace(haystack, start, len, replace);
1599 ret++;
1601 /* skip past whole match */
1602 g_match_info_fetch_pos(minfo, 0, NULL, &end);
1603 start = end - len + strlen(replace);
1604 g_match_info_free(minfo);
1606 g_match_info_free(minfo);
1607 return ret;
1611 /* Get project or default startup directory (if set), or NULL. */
1612 const gchar *utils_get_default_dir_utf8(void)
1614 if (app->project && !EMPTY(app->project->base_path))
1616 return app->project->base_path;
1619 if (!EMPTY(prefs.default_open_path))
1621 return prefs.default_open_path;
1623 return NULL;
1628 * Wraps @c spawn_sync(), which see.
1630 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1631 * @param argv The child's argument vector.
1632 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1633 * @param flags Ignored.
1634 * @param child_setup @girskip Ignored.
1635 * @param user_data @girskip Ignored.
1636 * @param std_out @out @optional The return location for child output, or @c NULL.
1637 * @param std_err @out @optional The return location for child error messages, or @c NULL.
1638 * @param exit_status @out @optional The child exit status, as returned by waitpid(), or @c NULL.
1639 * @param error The return location for error or @c NULL.
1641 * @return @c TRUE on success, @c FALSE if an error was set.
1643 GEANY_API_SYMBOL
1644 gboolean utils_spawn_sync(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1645 GSpawnChildSetupFunc child_setup, gpointer user_data, gchar **std_out,
1646 gchar **std_err, gint *exit_status, GError **error)
1648 GString *output = std_out ? g_string_new(NULL) : NULL;
1649 GString *errors = std_err ? g_string_new(NULL) : NULL;
1650 gboolean result = spawn_sync(dir, NULL, argv, env, NULL, output, errors, exit_status, error);
1652 if (std_out)
1653 *std_out = g_string_free(output, !result);
1655 if (std_err)
1656 *std_err = g_string_free(errors, !result);
1658 return result;
1663 * Wraps @c spawn_async(), which see.
1665 * @param dir @nullable The child's current working directory, or @c NULL to inherit parent's.
1666 * @param argv The child's argument vector.
1667 * @param env @nullable The child's environment, or @c NULL to inherit parent's.
1668 * @param flags Ignored.
1669 * @param child_setup @girskip Ignored.
1670 * @param user_data Ignored.
1671 * @param child_pid @out @nullable The return location for child process ID, 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_async(const gchar *dir, gchar **argv, gchar **env, GSpawnFlags flags,
1678 GSpawnChildSetupFunc child_setup, gpointer user_data, GPid *child_pid,
1679 GError **error)
1681 return spawn_async(dir, NULL, argv, env, child_pid, error);
1685 /* Returns "file:///" on Windows, "file://" everywhere else */
1686 const gchar *utils_get_uri_file_prefix(void)
1688 #ifdef G_OS_WIN32
1689 return "file:///";
1690 #else
1691 return "file://";
1692 #endif
1696 /* Retrieves the path for the given URI.
1697 * It returns:
1698 * - the path which was determined by g_filename_from_uri() or GIO
1699 * - NULL if the URI is non-local and gvfs-fuse is not installed
1700 * - a new copy of 'uri' if it is not an URI. */
1701 gchar *utils_get_path_from_uri(const gchar *uri)
1703 gchar *locale_filename;
1705 g_return_val_if_fail(uri != NULL, NULL);
1707 if (! utils_is_uri(uri))
1708 return g_strdup(uri);
1710 /* this will work only for 'file://' URIs */
1711 locale_filename = g_filename_from_uri(uri, NULL, NULL);
1712 /* g_filename_from_uri() failed, so we probably have a non-local URI */
1713 if (locale_filename == NULL)
1715 GFile *file = g_file_new_for_uri(uri);
1716 locale_filename = g_file_get_path(file);
1717 g_object_unref(file);
1718 if (locale_filename == NULL)
1720 geany_debug("The URI '%s' could not be resolved to a local path. This means "
1721 "that the URI is invalid or that you don't have gvfs-fuse installed.", uri);
1725 return locale_filename;
1729 gboolean utils_is_uri(const gchar *uri)
1731 g_return_val_if_fail(uri != NULL, FALSE);
1733 return (strstr(uri, "://") != NULL);
1737 /* path should be in locale encoding */
1738 gboolean utils_is_remote_path(const gchar *path)
1740 g_return_val_if_fail(path != NULL, FALSE);
1742 /* if path is an URI and it doesn't start "file://", we take it as remote */
1743 if (utils_is_uri(path) && strncmp(path, "file:", 5) != 0)
1744 return TRUE;
1746 #ifndef G_OS_WIN32
1748 static gchar *fuse_path = NULL;
1749 static gsize len = 0;
1751 if (G_UNLIKELY(fuse_path == NULL))
1753 fuse_path = g_build_filename(g_get_home_dir(), ".gvfs", NULL);
1754 len = strlen(fuse_path);
1756 /* Comparing the file path against a hardcoded path is not the most elegant solution
1757 * but for now it is better than nothing. Ideally, g_file_new_for_path() should create
1758 * proper GFile objects for Fuse paths, but it only does in future GVFS
1759 * versions (gvfs 1.1.1). */
1760 return (strncmp(path, fuse_path, len) == 0);
1762 #endif
1764 return FALSE;
1768 /* Remove all relative and untidy elements from the path of @a filename.
1769 * @param filename must be a valid absolute path.
1770 * @see utils_get_real_path() - also resolves links. */
1771 void utils_tidy_path(gchar *filename)
1773 GString *str;
1774 const gchar *needle;
1775 gboolean preserve_double_backslash = FALSE;
1777 g_return_if_fail(g_path_is_absolute(filename));
1779 str = g_string_new(filename);
1781 if (str->len >= 2 && strncmp(str->str, "\\\\", 2) == 0)
1782 preserve_double_backslash = TRUE;
1784 #ifdef G_OS_WIN32
1785 /* using MSYS we can get Unix-style separators */
1786 utils_string_replace_all(str, "/", G_DIR_SEPARATOR_S);
1787 #endif
1788 /* replace "/./" and "//" */
1789 utils_string_replace_all(str, G_DIR_SEPARATOR_S "." G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1790 utils_string_replace_all(str, G_DIR_SEPARATOR_S G_DIR_SEPARATOR_S, G_DIR_SEPARATOR_S);
1792 if (preserve_double_backslash)
1793 g_string_prepend(str, "\\");
1795 /* replace "/../" */
1796 needle = G_DIR_SEPARATOR_S ".." G_DIR_SEPARATOR_S;
1797 while (1)
1799 const gchar *c = strstr(str->str, needle);
1800 if (c == NULL)
1801 break;
1802 else
1804 gssize pos, sub_len;
1806 pos = c - str->str;
1807 if (pos <= 3)
1808 break; /* bad path */
1810 /* replace "/../" */
1811 g_string_erase(str, pos, strlen(needle));
1812 g_string_insert_c(str, pos, G_DIR_SEPARATOR);
1814 /* search for last "/" before found "/../" */
1815 c = g_strrstr_len(str->str, pos, G_DIR_SEPARATOR_S);
1816 sub_len = pos - (c - str->str);
1817 if (! c)
1818 break; /* bad path */
1820 pos = c - str->str; /* position of previous "/" */
1821 g_string_erase(str, pos, sub_len);
1824 if (str->len <= strlen(filename))
1825 memcpy(filename, str->str, str->len + 1);
1826 else
1827 g_warn_if_reached();
1828 g_string_free(str, TRUE);
1833 * Removes characters from a string, in place.
1835 * @param string String to search.
1836 * @param chars Characters to remove.
1838 * @return @a string - return value is only useful when nesting function calls, e.g.:
1839 * @code str = utils_str_remove_chars(g_strdup("f_o_o"), "_"); @endcode
1841 * @see @c g_strdelimit.
1843 GEANY_API_SYMBOL
1844 gchar *utils_str_remove_chars(gchar *string, const gchar *chars)
1846 const gchar *r;
1847 gchar *w = string;
1849 g_return_val_if_fail(string, NULL);
1850 if (G_UNLIKELY(EMPTY(chars)))
1851 return string;
1853 foreach_str(r, string)
1855 if (!strchr(chars, *r))
1856 *w++ = *r;
1858 *w = 0x0;
1859 return string;
1863 /* Gets list of sorted filenames with no path and no duplicates from user and system config */
1864 GSList *utils_get_config_files(const gchar *subdir)
1866 gchar *path = g_build_path(G_DIR_SEPARATOR_S, app->configdir, subdir, NULL);
1867 GSList *list = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1868 GSList *syslist, *node;
1870 if (!list)
1872 utils_mkdir(path, FALSE);
1874 SETPTR(path, g_build_path(G_DIR_SEPARATOR_S, app->datadir, subdir, NULL));
1875 syslist = utils_get_file_list_full(path, FALSE, FALSE, NULL);
1876 /* merge lists */
1877 list = g_slist_concat(list, syslist);
1879 list = g_slist_sort(list, (GCompareFunc) utils_str_casecmp);
1880 /* remove duplicates (next to each other after sorting) */
1881 foreach_slist(node, list)
1883 if (node->next && utils_str_equal(node->next->data, node->data))
1885 GSList *old = node->next;
1887 g_free(old->data);
1888 node->next = old->next;
1889 g_slist_free1(old);
1892 g_free(path);
1893 return list;
1897 /* Suffix can be NULL or a string which should be appended to the Help URL like
1898 * an anchor link, e.g. "#some_anchor". */
1899 gchar *utils_get_help_url(const gchar *suffix)
1901 gchar *uri;
1902 const gchar *uri_file_prefix = utils_get_uri_file_prefix();
1903 gint skip = strlen(uri_file_prefix);
1905 uri = g_strconcat(uri_file_prefix, app->docdir, "/index.html", NULL);
1906 #ifdef G_OS_WIN32
1907 g_strdelimit(uri, "\\", '/'); /* replace '\\' by '/' */
1908 #endif
1910 if (! g_file_test(uri + skip, G_FILE_TEST_IS_REGULAR))
1911 { /* fall back to online documentation if it is not found on the hard disk */
1912 g_free(uri);
1913 uri = g_strconcat(GEANY_HOMEPAGE, "manual/", PACKAGE_VERSION, "/index.html", NULL);
1916 if (suffix != NULL)
1918 SETPTR(uri, g_strconcat(uri, suffix, NULL));
1921 return uri;
1925 static gboolean str_in_array(const gchar **haystack, const gchar *needle)
1927 const gchar **p;
1929 for (p = haystack; *p != NULL; ++p)
1931 if (utils_str_equal(*p, needle))
1932 return TRUE;
1934 return FALSE;
1939 * Copies the current environment into a new array.
1940 * @a exclude_vars is a @c NULL-terminated array of variable names which should be not copied.
1941 * All further arguments are key, value pairs of variables which should be added to
1942 * the environment.
1944 * The argument list must be @c NULL-terminated.
1946 * @param exclude_vars @c NULL-terminated array of variable names to exclude.
1947 * @param first_varname Name of the first variable to copy into the new array.
1948 * @param ... Key-value pairs of variable names and values, @c NULL-terminated.
1950 * @return @transfer{full} The new environment array. Use @c g_strfreev() to free it.
1952 GEANY_API_SYMBOL
1953 gchar **utils_copy_environment(const gchar **exclude_vars, const gchar *first_varname, ...)
1955 gchar **result;
1956 gchar **p;
1957 gchar **env;
1958 va_list args;
1959 const gchar *key, *value;
1960 guint n, o;
1962 /* count the additional variables */
1963 va_start(args, first_varname);
1964 for (o = 1; va_arg(args, gchar*) != NULL; o++);
1965 va_end(args);
1966 /* the passed arguments should be even (key, value pairs) */
1967 g_return_val_if_fail(o % 2 == 0, NULL);
1969 o /= 2;
1971 /* get all the environ variables */
1972 env = g_listenv();
1974 /* create an array large enough to hold the new environment */
1975 n = g_strv_length(env);
1976 /* 'n + o + 1' could leak a little bit when exclude_vars is set */
1977 result = g_new(gchar *, n + o + 1);
1979 /* copy the environment */
1980 for (n = 0, p = env; *p != NULL; ++p)
1982 /* copy the variable */
1983 value = g_getenv(*p);
1984 if (G_LIKELY(value != NULL))
1986 /* skip excluded variables */
1987 if (exclude_vars != NULL && str_in_array(exclude_vars, *p))
1988 continue;
1990 result[n++] = g_strconcat(*p, "=", value, NULL);
1993 g_strfreev(env);
1995 /* now add additional variables */
1996 va_start(args, first_varname);
1997 key = first_varname;
1998 value = va_arg(args, gchar*);
1999 while (key != NULL)
2001 result[n++] = g_strconcat(key, "=", value, NULL);
2003 key = va_arg(args, gchar*);
2004 if (key == NULL)
2005 break;
2006 value = va_arg(args, gchar*);
2008 va_end(args);
2010 result[n] = NULL;
2012 return result;
2016 /* Joins @a first and @a second into a new string vector, freeing the originals.
2017 * The original contents are reused. */
2018 gchar **utils_strv_join(gchar **first, gchar **second)
2020 gchar **strv;
2021 gchar **rptr, **wptr;
2023 if (!first)
2024 return second;
2025 if (!second)
2026 return first;
2028 strv = g_new0(gchar*, g_strv_length(first) + g_strv_length(second) + 1);
2029 wptr = strv;
2031 foreach_strv(rptr, first)
2032 *wptr++ = *rptr;
2033 foreach_strv(rptr, second)
2034 *wptr++ = *rptr;
2036 g_free(first);
2037 g_free(second);
2038 return strv;
2041 /* * Returns the common prefix in a list of strings.
2043 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2045 * @param strv The list of strings to process.
2046 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2048 * @return The common prefix that is part of all strings (maybe empty), or NULL if an empty list
2049 * was passed in.
2051 GEANY_EXPORT_SYMBOL
2052 gchar *utils_strv_find_common_prefix(gchar **strv, gssize strv_len)
2054 gsize num;
2056 if (strv_len == 0)
2057 return NULL;
2059 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2061 for (gsize i = 0; strv[0][i]; i++)
2063 for (gsize j = 1; j < num; j++)
2065 if (strv[j][i] != strv[0][i])
2067 /* return prefix on first mismatch */
2068 return g_strndup(strv[0], i);
2073 return g_strdup(strv[0]);
2077 /* * Returns the longest common substring in a list of strings.
2079 * The size of the list may be given explicitely, but defaults to @c g_strv_length(strv).
2081 * @param strv The list of strings to process.
2082 * @param strv_len The number of strings contained in @a strv. Can be -1 if it's terminated by @c NULL.
2084 * @return The common prefix that is part of all strings.
2086 GEANY_EXPORT_SYMBOL
2087 gchar *utils_strv_find_lcs(gchar **strv, gssize strv_len, const gchar *delim)
2089 gchar *first, *_sub, *sub;
2090 gsize num;
2091 gsize n_chars;
2092 gsize len;
2093 gsize max = 0;
2094 char *lcs;
2095 gsize found;
2097 if (strv_len == 0)
2098 return NULL;
2100 num = (strv_len == -1) ? g_strv_length(strv) : (gsize) strv_len;
2102 first = strv[0];
2103 len = strlen(first);
2105 /* sub is the working area where substrings from first are copied to */
2106 sub = g_malloc(len+1);
2107 lcs = g_strdup("");
2108 foreach_str(_sub, first)
2110 gsize chars_left = len - (_sub - first);
2111 /* No point in continuing if the remainder is too short */
2112 if (max > chars_left)
2113 break;
2114 /* If delimiters are given, we only need to compare substrings which start and
2115 * end with one of them, so skip any non-delim chars at front ... */
2116 if (NZV(delim) && (strchr(delim, _sub[0]) == NULL))
2117 continue;
2118 for (n_chars = 1; n_chars <= chars_left; n_chars++)
2120 if (NZV(delim))
2121 { /* ... and advance to the next delim char at the end, if any */
2122 if (!_sub[n_chars] || strchr(delim, _sub[n_chars]) == NULL)
2123 continue;
2124 n_chars += 1;
2126 g_strlcpy(sub, _sub, n_chars+1);
2127 found = 1;
2128 for (gsize i = 1; i < num; i++)
2130 if (strstr(strv[i], sub) == NULL)
2131 break;
2132 found++;
2134 if (found == num && n_chars > max)
2136 max = n_chars;
2137 SETPTR(lcs, g_strdup(sub));
2141 g_free(sub);
2143 return lcs;
2147 /** Transform file names in a list to be shorter.
2149 * This function takes a list of file names (probably with absolute paths), and
2150 * transforms the paths such that they are short but still unique. This is intended
2151 * for dialogs which present the file list to the user, where the base name may result
2152 * in duplicates (showing the full path might be inappropriate).
2154 * The algorthm strips the common prefix (e-g. the user's home directory) and
2155 * replaces the longest common substring with an ellipsis ("...").
2157 * @param file_names @array{length=file_names_len} The list of strings to process.
2158 * @param file_names_len The number of strings contained in @a file_names. Can be -1 if it's
2159 * terminated by @c NULL.
2160 * @return @transfer{full} A newly-allocated array of transformed paths strings, terminated by
2161 @c NULL. Use @c g_strfreev() to free it.
2163 * @since 1.34 (API 239)
2165 GEANY_API_SYMBOL
2166 gchar **utils_strv_shorten_file_list(gchar **file_names, gssize file_names_len)
2168 gsize num;
2169 gsize i;
2170 gchar *prefix, *lcs, *end;
2171 gchar **names;
2172 gsize prefix_len = 0, lcs_len = 0;
2174 if (file_names_len == 0)
2175 return g_new0(gchar *, 1);
2177 g_return_val_if_fail(file_names != NULL, NULL);
2179 num = (file_names_len == -1) ? g_strv_length(file_names) : (gsize) file_names_len;
2180 /* Always include a terminating NULL, enables easy freeing with g_strfreev()
2181 * We just copy the pointers so we can advance them here. But don't
2182 * forget to duplicate the strings before returning.
2184 names = g_new(gchar *, num + 1);
2185 memcpy(names, file_names, num * sizeof(gchar *));
2186 /* Always include a terminating NULL, enables easy freeing with g_strfreev() */
2187 names[num] = NULL;
2189 /* First: determine the common prefix, that will be stripped.
2190 * We only want to strip full path components, including the trailing slash.
2191 * Except if the component is just "/".
2193 prefix = utils_strv_find_common_prefix(names, num);
2194 end = strrchr(prefix, G_DIR_SEPARATOR);
2195 if (end && end > prefix)
2197 prefix_len = end - prefix + 1; /* prefix_len includes the trailing slash */
2198 for (i = 0; i < num; i++)
2199 names[i] += prefix_len;
2202 /* Second: determine the longest common substring (lcs), that will be ellipsized. Again,
2203 * we look only for full path compnents so that we ellipsize between separators. This implies
2204 * that the file name cannot be ellipsized which is desirable anyway.
2206 lcs = utils_strv_find_lcs(names, num, G_DIR_SEPARATOR_S"/");
2207 if (lcs)
2209 lcs_len = strlen(lcs);
2210 /* Don't bother for tiny common parts (which are often just "." or "/"). Beware
2211 * that lcs includes the enclosing dir separators so the part must be at least 5 chars
2212 * to be eligible for ellipsizing.
2214 if (lcs_len < 7)
2215 lcs_len = 0;
2218 /* Last: build the shortened list of unique file names */
2219 for (i = 0; i < num; i++)
2221 if (lcs_len == 0)
2222 { /* no lcs, copy without prefix */
2223 names[i] = g_strdup(names[i]);
2225 else
2227 const gchar *lcs_start = strstr(names[i], lcs);
2228 const gchar *lcs_end = lcs_start + lcs_len;
2229 /* Dir seperators are included in lcs but shouldn't be elipsized. */
2230 names[i] = g_strdup_printf("%.*s...%s", (int)(lcs_start - names[i] + 1), names[i], lcs_end - 1);
2234 g_free(lcs);
2235 g_free(prefix);
2237 return names;
2241 /* Try to parse a date using g_date_set_parse(). It doesn't take any format hint,
2242 * obviously g_date_set_parse() uses some magic.
2243 * The returned GDate object must be freed. */
2244 GDate *utils_parse_date(const gchar *input)
2246 GDate *date = g_date_new();
2248 g_date_set_parse(date, input);
2250 if (g_date_valid(date))
2251 return date;
2253 g_date_free(date);
2254 return NULL;
2258 gchar *utils_parse_and_format_build_date(const gchar *input)
2260 gchar date_buf[255];
2261 GDate *date = utils_parse_date(input);
2263 if (date != NULL)
2265 g_date_strftime(date_buf, sizeof(date_buf), GEANY_TEMPLATES_FORMAT_DATE, date);
2266 g_date_free(date);
2267 return g_strdup(date_buf);
2270 return g_strdup(input);
2274 gchar *utils_get_user_config_dir(void)
2276 #ifdef G_OS_WIN32
2277 return win32_get_user_config_dir();
2278 #else
2279 return g_build_filename(g_get_user_config_dir(), "geany", NULL);
2280 #endif
2284 static gboolean is_osx_bundle(void)
2286 #ifdef MAC_INTEGRATION
2287 gchar *bundle_id = gtkosx_application_get_bundle_id();
2288 if (bundle_id)
2290 g_free(bundle_id);
2291 return TRUE;
2293 #endif
2294 return FALSE;
2298 const gchar *utils_resource_dir(GeanyResourceDirType type)
2300 static const gchar *resdirs[RESOURCE_DIR_COUNT] = {NULL};
2302 if (!resdirs[RESOURCE_DIR_DATA])
2304 #ifdef G_OS_WIN32
2305 gchar *prefix = win32_get_installation_dir();
2307 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "data", NULL);
2308 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2309 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2310 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2311 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2312 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2313 g_free(prefix);
2314 #else
2315 if (is_osx_bundle())
2317 # ifdef MAC_INTEGRATION
2318 gchar *prefix = gtkosx_application_get_resource_path();
2320 resdirs[RESOURCE_DIR_DATA] = g_build_filename(prefix, "share", "geany", NULL);
2321 resdirs[RESOURCE_DIR_ICON] = g_build_filename(prefix, "share", "icons", NULL);
2322 resdirs[RESOURCE_DIR_DOC] = g_build_filename(prefix, "share", "doc", "geany", "html", NULL);
2323 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(prefix, "share", "locale", NULL);
2324 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(prefix, "lib", "geany", NULL);
2325 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(prefix, "libexec", "geany", NULL);
2326 g_free(prefix);
2327 # endif
2329 else
2331 resdirs[RESOURCE_DIR_DATA] = g_build_filename(GEANY_DATADIR, "geany", NULL);
2332 resdirs[RESOURCE_DIR_ICON] = g_build_filename(GEANY_DATADIR, "icons", NULL);
2333 resdirs[RESOURCE_DIR_DOC] = g_build_filename(GEANY_DOCDIR, "html", NULL);
2334 resdirs[RESOURCE_DIR_LOCALE] = g_build_filename(GEANY_LOCALEDIR, NULL);
2335 resdirs[RESOURCE_DIR_PLUGIN] = g_build_filename(GEANY_LIBDIR, "geany", NULL);
2336 resdirs[RESOURCE_DIR_LIBEXEC] = g_build_filename(GEANY_LIBEXECDIR, "geany", NULL);
2338 #endif
2341 return resdirs[type];
2345 void utils_start_new_geany_instance(const gchar *doc_path)
2347 const gchar *command = is_osx_bundle() ? "open" : "geany";
2348 gchar *exec_path = g_find_program_in_path(command);
2350 if (exec_path)
2352 GError *err = NULL;
2353 const gchar *argv[6]; // max args + 1
2354 gint argc = 0;
2356 argv[argc++] = exec_path;
2357 if (is_osx_bundle())
2359 argv[argc++] = "-n";
2360 argv[argc++] = "-a";
2361 argv[argc++] = "Geany";
2362 argv[argc++] = doc_path;
2364 else
2366 argv[argc++] = "-i";
2367 argv[argc++] = doc_path;
2369 argv[argc] = NULL;
2371 if (!utils_spawn_async(NULL, (gchar**) argv, NULL, 0, NULL, NULL, NULL, &err))
2373 g_printerr("Unable to open new window: %s\n", err->message);
2374 g_error_free(err);
2376 g_free(exec_path);
2378 else
2379 g_printerr("Unable to find 'geany'\n");
2384 * Get a link-dereferenced, absolute version of a file name.
2386 * This is similar to the POSIX `realpath` function when passed a
2387 * @c NULL argument.
2389 * @warning This function suffers the same problems as the POSIX
2390 * function `realpath()`, namely that it's impossible to determine
2391 * a suitable size for the returned buffer, and so it's limited to a
2392 * maximum of `PATH_MAX`.
2394 * @param file_name The file name to get the real path of.
2396 * @return A newly-allocated string containing the real path which
2397 * should be freed with `g_free()` when no longer needed, or @c NULL
2398 * if the real path cannot be obtained.
2400 * @since 1.32 (API 235)
2402 GEANY_API_SYMBOL
2403 gchar *utils_get_real_path(const gchar *file_name)
2405 return tm_get_real_path(file_name);
2410 * Get a string describing the OS.
2412 * If the OS can be determined, a string which describes the OS will
2413 * be returned. If no OS can be determined then `NULL` will be returned.
2415 * @note The format of the returned string is unspecified and is only
2416 * meant to provide diagnostic information to the user.
2418 * @return A newly-allocated string containing a description of the
2419 * OS if it can be determined or `NULL` if it cannot.
2421 * @since 1.37
2423 gchar *utils_get_os_info_string(void)
2425 gchar *os_info = NULL;
2427 #if GLIB_CHECK_VERSION(2, 64, 0)
2428 # if ! defined(__APPLE__)
2429 /* on non-macOS operating systems */
2431 GString *os_str;
2432 gchar *pretty_name;
2433 gchar *code_name;
2435 pretty_name = g_get_os_info(G_OS_INFO_KEY_PRETTY_NAME);
2436 if (pretty_name == NULL)
2437 return NULL;
2439 os_str = g_string_new(pretty_name);
2440 g_free(pretty_name);
2442 code_name = g_get_os_info(G_OS_INFO_KEY_VERSION_CODENAME);
2443 if (code_name != NULL)
2445 g_string_append_printf(os_str, " (%s)", code_name);
2446 g_free(code_name);
2449 os_info = g_string_free(os_str, FALSE);
2451 # else
2452 /* on macOS, only `G_OS_INFO_KEY_NAME` is supported and returns the
2453 * fixed string `macOS` */
2454 os_info = g_get_os_info(G_OS_INFO_KEY_NAME);
2455 # endif
2456 #else
2457 /* if g_get_os_info() is not available, do it the old-fashioned way */
2458 # if defined(_WIN64)
2459 os_info = g_strdup("Microsoft Windows (64-bit)");
2460 # elif defined(_WIN32)
2461 os_info = g_strdup("Microsoft Windows");
2462 # elif defined(__APPLE__)
2463 os_info = g_strdup("Apple macOS");
2464 # elif defined(__linux__)
2465 os_info = g_strdup("Linux");
2466 # elif defined(__FreeBSD__)
2467 os_info = g_strdup("FreeBSD");
2468 # elif defined(__ANDROID__)
2469 os_info = g_strdup("Android");
2470 # endif
2471 #endif
2473 return os_info;