Blender V4.5
appdir.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include <cstdio>
12#include <cstdlib>
13#include <cstring>
14
15#include "BLI_fileops.h"
16#include "BLI_fileops_types.h"
17#include "BLI_listbase.h"
18#include "BLI_path_utils.hh"
19#include "BLI_string.h"
20#include "BLI_string_utils.hh"
21#include "BLI_tempfile.h"
22#include "BLI_utildefines.h"
23#include "BLI_vector.hh"
24
25#include "BKE_appdir.hh" /* own include */
26#include "BKE_blender_version.h"
27
28#include "BLT_translation.hh"
29
30#include "GHOST_Path-api.hh"
31
32#include "CLG_log.h"
33
34#ifdef WIN32
35# include "BLI_string_utf8.h"
36# include "utf_winfunc.hh"
37# include "utfconv.hh"
38# include <io.h>
39# ifdef _WIN32_IE
40# undef _WIN32_IE
41# endif
42# define _WIN32_IE 0x0501
43# include "BLI_winstuff.h"
44# include <shlobj.h>
45# include <windows.h>
46#else /* non windows */
47# ifdef WITH_BINRELOC
48# include "binreloc.h"
49# endif
50/* #mkdtemp on OSX (and probably all *BSD?), not worth making specific check for this OS. */
51# include <unistd.h>
52#endif /* !WIN32 */
53
54static const char _str_null[] = "(null)";
55#define STR_OR_FALLBACK(a) ((a) ? (a) : _str_null)
56
57/* -------------------------------------------------------------------- */
60
61/* local */
62static CLG_LogRef LOG = {"bke.appdir"};
63
64static struct {
79
81
82/* -------------------------------------------------------------------- */
85
86#ifndef NDEBUG
87static bool is_appdir_init = false;
88# define ASSERT_IS_INIT() BLI_assert(is_appdir_init)
89#else
90# define ASSERT_IS_INIT() ((void)0)
91#endif /* NDEBUG */
92
94{
95#ifndef NDEBUG
96 BLI_assert(is_appdir_init == false);
97 is_appdir_init = true;
98#endif
99}
100
102{
103 /* System paths can be created on-demand by calls to this API. So they need to be properly
104 * disposed of here. Note that there may be several calls to this in `exit` process
105 * (e.g. `wm_init/wm_exit` will currently both call GHOST API directly,
106 * & `BKE_appdir_init/_exit`). */
108#ifndef NDEBUG
109 BLI_assert(is_appdir_init == true);
110 is_appdir_init = false;
111#endif
112}
113
115
116/* -------------------------------------------------------------------- */
119
123static char *blender_version_decimal(const int version)
124{
125 static char version_str[5];
126 BLI_assert(version < 1000);
127 SNPRINTF(version_str, "%d.%d", version / 100, version % 100);
128 return version_str;
129}
130
132
133/* -------------------------------------------------------------------- */
136
138{
139#ifndef WIN32
140 return BLI_dir_home();
141#else /* Windows */
142 static char documentfolder[FILE_MAXDIR];
143
144 if (BKE_appdir_folder_documents(documentfolder)) {
145 return documentfolder;
146 }
147
148 return nullptr;
149#endif /* WIN32 */
150}
151
153{
154#ifndef WIN32
155 return "/";
156#else
157 static char root[4];
159 return root;
160#endif
161}
162
164{
165 const char *path = BKE_appdir_folder_default();
166 if (path == nullptr) {
167 path = BKE_appdir_folder_root();
168 }
169 return path;
170}
171
173{
174 dir[0] = '\0';
175
176 const char *documents_path = GHOST_getUserSpecialDir(GHOST_kUserSpecialDirDocuments);
177
178 /* Usual case: Ghost gave us the documents path. We're done here. */
179 if (documents_path && BLI_is_dir(documents_path)) {
180 BLI_strncpy(dir, documents_path, FILE_MAXDIR);
181 return true;
182 }
183
184 /* Ghost couldn't give us a documents path, let's try if we can find it ourselves. */
185
186 const char *home_path = BLI_dir_home();
187 if (!home_path || !BLI_is_dir(home_path)) {
188 return false;
189 }
190
191 char try_documents_path[FILE_MAXDIR];
192 /* Own attempt at getting a valid Documents path. */
193 BLI_path_join(try_documents_path, sizeof(try_documents_path), home_path, N_("Documents"));
194 if (!BLI_is_dir(try_documents_path)) {
195 return false;
196 }
197
198 BLI_strncpy(dir, try_documents_path, FILE_MAXDIR);
199 return true;
200}
201
202bool BKE_appdir_folder_caches(char *path, const size_t path_maxncpy)
203{
204 path[0] = '\0';
205
206 const char *caches_root_path = GHOST_getUserSpecialDir(GHOST_kUserSpecialDirCaches);
207 if (caches_root_path == nullptr || !BLI_is_dir(caches_root_path)) {
208 caches_root_path = BKE_tempdir_base();
209 }
210 if (caches_root_path == nullptr || !BLI_is_dir(caches_root_path)) {
211 return false;
212 }
213
214#ifdef WIN32
216 path, path_maxncpy, caches_root_path, "Blender Foundation", "Blender", "Cache", SEP_STR);
217#elif defined(__APPLE__)
218 BLI_path_join(path, path_maxncpy, caches_root_path, "Blender", SEP_STR);
219#else /* __linux__ */
220 BLI_path_join(path, path_maxncpy, caches_root_path, "blender", SEP_STR);
221#endif
222
223 return true;
224}
225
226bool BKE_appdir_font_folder_default(char *dir, size_t dir_maxncpy)
227{
228 char test_dir[FILE_MAXDIR];
229 test_dir[0] = '\0';
230
231#ifdef WIN32
232 wchar_t wpath[MAX_PATH];
233 if (SHGetSpecialFolderPathW(0, wpath, CSIDL_FONTS, 0)) {
234 BLI_strncpy_wchar_as_utf8(test_dir, wpath, sizeof(test_dir));
235 }
236#elif defined(__APPLE__)
237 if (const char *home_dir = BLI_dir_home()) {
238 BLI_path_join(test_dir, sizeof(test_dir), home_dir, "Library/Fonts");
239 }
240#else
241 STRNCPY(test_dir, "/usr/share/fonts");
242#endif
243
244 if (test_dir[0] && BLI_exists(test_dir)) {
245 BLI_strncpy(dir, test_dir, dir_maxncpy);
246 return true;
247 }
248 return false;
249}
250
252
253/* -------------------------------------------------------------------- */
256
272static bool test_path(char *targetpath,
273 size_t targetpath_maxncpy,
274 const bool check_is_dir,
275 const char *path_base,
276 const char *folder_name,
277 const char *subfolder_name)
278{
280
281 /* Only the last argument should be nullptr. */
282 BLI_assert(!(folder_name == nullptr && (subfolder_name != nullptr)));
283 const char *path_array[] = {path_base, folder_name, subfolder_name};
284 const int path_array_num = (folder_name ? (subfolder_name ? 3 : 2) : 1);
285 BLI_path_join_array(targetpath, targetpath_maxncpy, path_array, path_array_num);
286 if (check_is_dir == false) {
287 CLOG_INFO(&LOG, 3, "using without test: '%s'", targetpath);
288 return true;
289 }
290
291 if (BLI_is_dir(targetpath)) {
292 CLOG_INFO(&LOG, 3, "found '%s'", targetpath);
293 return true;
294 }
295
296 CLOG_INFO(&LOG, 3, "missing '%s'", targetpath);
297
298 /* Path not found, don't accidentally use it,
299 * otherwise call this function with `check_is_dir` set to false. */
300 targetpath[0] = '\0';
301 return false;
302}
303
312static bool test_env_path(char *path, const char *envvar, const bool check_is_dir)
313{
315
316 const char *env_path = envvar ? BLI_getenv(envvar) : nullptr;
317 if (!env_path) {
318 return false;
319 }
320
321 BLI_strncpy(path, env_path, FILE_MAX);
322
323 if (check_is_dir == false) {
324 CLOG_INFO(&LOG, 3, "using env '%s' without test: '%s'", envvar, env_path);
325 return true;
326 }
327
328 if (BLI_is_dir(env_path)) {
329 CLOG_INFO(&LOG, 3, "env '%s' found: %s", envvar, env_path);
330 return true;
331 }
332
333 CLOG_INFO(&LOG, 3, "env '%s' missing: %s", envvar, env_path);
334
335 /* Path not found, don't accidentally use it,
336 * otherwise call this function with `check_is_dir` set to false. */
337 path[0] = '\0';
338 return false;
339}
340
353static bool get_path_local_ex(char *targetpath,
354 size_t targetpath_maxncpy,
355 const char *folder_name,
356 const char *subfolder_name,
357 const int version,
358 const bool check_is_dir)
359{
360 char relfolder[FILE_MAX];
361
362 CLOG_INFO(&LOG,
363 3,
364 "folder='%s', subfolder='%s'",
365 STR_OR_FALLBACK(folder_name),
366 STR_OR_FALLBACK(subfolder_name));
367
368 if (folder_name) { /* `subfolder_name` may be nullptr. */
369 const char *path_array[] = {folder_name, subfolder_name};
370 const int path_array_num = subfolder_name ? 2 : 1;
371 BLI_path_join_array(relfolder, sizeof(relfolder), path_array, path_array_num);
372 }
373 else {
374 relfolder[0] = '\0';
375 }
376
377 /* Try `{g_app.program_dirname}/3.xx/{folder_name}` the default directory
378 * for a portable distribution. See `WITH_INSTALL_PORTABLE` build-option. */
379 const char *path_base = g_app.program_dirname;
380#if defined(__APPLE__) && !defined(WITH_PYTHON_MODULE)
381 /* Due new code-sign situation in OSX > 10.9.5
382 * we must move the blender_version dir with contents to Resources.
383 * Add 4 + 9 for the temporary `/../` path & `Resources`. */
384 char osx_resourses[FILE_MAX + 4 + 9];
385 BLI_path_join(osx_resourses, sizeof(osx_resourses), g_app.program_dirname, "..", "Resources");
386 /* Remove the '/../' added above. */
387 BLI_path_normalize_native(osx_resourses);
388 path_base = osx_resourses;
389#endif
390 return test_path(targetpath,
391 targetpath_maxncpy,
392 check_is_dir,
393 path_base,
394 (version) ? blender_version_decimal(version) : relfolder,
395 (version) ? relfolder : nullptr);
396}
397static bool get_path_local(char *targetpath,
398 size_t targetpath_maxncpy,
399 const char *folder_name,
400 const char *subfolder_name)
401{
402 const int version = BLENDER_VERSION;
403 const bool check_is_dir = true;
404 return get_path_local_ex(
405 targetpath, targetpath_maxncpy, folder_name, subfolder_name, version, check_is_dir);
406}
407
417static bool get_path_environment_ex(char *targetpath,
418 size_t targetpath_maxncpy,
419 const char *subfolder_name,
420 const char *envvar,
421 const bool check_is_dir)
422{
423 char user_path[FILE_MAX];
424
425 if (test_env_path(user_path, envvar, check_is_dir)) {
426 /* Note that `subfolder_name` may be nullptr, in this case we use `user_path` as-is. */
427 return test_path(
428 targetpath, targetpath_maxncpy, check_is_dir, user_path, subfolder_name, nullptr);
429 }
430 return false;
431}
432static bool get_path_environment(char *targetpath,
433 size_t targetpath_maxncpy,
434 const char *subfolder_name,
435 const char *envvar)
436{
437 const bool check_is_dir = true;
439 targetpath, targetpath_maxncpy, subfolder_name, envvar, check_is_dir);
440}
441
443 const char *envvar,
444 const bool check_is_dir)
445{
447 const char *env_path = envvar ? BLI_getenv(envvar) : nullptr;
448 if (!env_path) {
449 return paths;
450 }
451
452#ifdef _WIN32
453 const char separator = ';';
454#else
455 const char separator = ':';
456#endif
457
458 const char *char_begin = env_path;
459 const char *char_end = BLI_strchr_or_end(char_begin, separator);
460 while (char_begin[0]) {
461 const size_t base_path_len = char_end - char_begin;
462 if (base_path_len > 0 && base_path_len < PATH_MAX) {
463 char base_path[PATH_MAX];
464 memcpy(base_path, char_begin, base_path_len);
465 base_path[base_path_len] = '\0';
466
467 char path[PATH_MAX];
468 if (test_path(path, sizeof(path), check_is_dir, base_path, subfolder_name, nullptr)) {
469 paths.append(path);
470 }
471 }
472 char_begin = char_end[0] ? char_end + 1 : char_end;
473 char_end = BLI_strchr_or_end(char_begin, separator);
474 }
475
476 return paths;
477}
478
489static bool get_path_user_ex(char *targetpath,
490 size_t targetpath_maxncpy,
491 const char *folder_name,
492 const char *subfolder_name,
493 const int version,
494 const bool check_is_dir)
495{
496 char user_path[FILE_MAX];
497
498 /* Environment variable override. */
499 if (test_env_path(user_path, "BLENDER_USER_RESOURCES", check_is_dir)) {
500 /* Pass. */
501 }
502 /* Portable install, to store user files next to Blender executable. */
503 else if (get_path_local_ex(user_path, sizeof(user_path), "portable", nullptr, 0, true)) {
504 /* Pass. */
505 }
506 else {
507 user_path[0] = '\0';
508
509 const char *user_base_path = GHOST_getUserDir(version, blender_version_decimal(version));
510 if (user_base_path) {
511 STRNCPY(user_path, user_base_path);
512 }
513 }
514
515 if (!user_path[0]) {
516 return false;
517 }
518
519 CLOG_INFO(&LOG,
520 3,
521 "'%s', folder='%s', subfolder='%s'",
522 user_path,
523 STR_OR_FALLBACK(folder_name),
524 STR_OR_FALLBACK(subfolder_name));
525
526 /* `subfolder_name` may be nullptr. */
527 return test_path(
528 targetpath, targetpath_maxncpy, check_is_dir, user_path, folder_name, subfolder_name);
529}
530static bool get_path_user(char *targetpath,
531 size_t targetpath_maxncpy,
532 const char *folder_name,
533 const char *subfolder_name)
534{
535 const int version = BLENDER_VERSION;
536 const bool check_is_dir = true;
537 return get_path_user_ex(
538 targetpath, targetpath_maxncpy, folder_name, subfolder_name, version, check_is_dir);
539}
540
551static bool get_path_system_ex(char *targetpath,
552 size_t targetpath_maxncpy,
553 const char *folder_name,
554 const char *subfolder_name,
555 const int version,
556 const bool check_is_dir)
557{
558 char system_path[FILE_MAX];
559
560 if (test_env_path(system_path, "BLENDER_SYSTEM_RESOURCES", check_is_dir)) {
561 /* Pass. */
562 }
563 else {
564 system_path[0] = '\0';
565 const char *system_base_path = GHOST_getSystemDir(version, blender_version_decimal(version));
566 if (system_base_path) {
567 STRNCPY(system_path, system_base_path);
568 }
569 }
570
571 if (!system_path[0]) {
572 return false;
573 }
574
575 CLOG_INFO(&LOG,
576 3,
577 "'%s', folder='%s', subfolder='%s'",
578 system_path,
579 STR_OR_FALLBACK(folder_name),
580 STR_OR_FALLBACK(subfolder_name));
581
582 /* Try `$BLENDERPATH/folder_name/subfolder_name`, `subfolder_name` may be nullptr. */
583 return test_path(
584 targetpath, targetpath_maxncpy, check_is_dir, system_path, folder_name, subfolder_name);
585}
586
587static bool get_path_system(char *targetpath,
588 size_t targetpath_maxncpy,
589 const char *folder_name,
590 const char *subfolder_name)
591{
592 const int version = BLENDER_VERSION;
593 const bool check_is_dir = true;
594 return get_path_system_ex(
595 targetpath, targetpath_maxncpy, folder_name, subfolder_name, version, check_is_dir);
596}
597
599
600/* -------------------------------------------------------------------- */
603
604bool BKE_appdir_folder_id_ex(const int folder_id,
605 const char *subfolder,
606 char *path,
607 size_t path_maxncpy)
608{
609 switch (folder_id) {
610 case BLENDER_DATAFILES: /* general case */
611 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_DATAFILES")) {
612 break;
613 }
614 if (get_path_user(path, path_maxncpy, "datafiles", subfolder)) {
615 break;
616 }
617 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
618 break;
619 }
620 if (get_path_system(path, path_maxncpy, "datafiles", subfolder)) {
621 break;
622 }
623 if (get_path_local(path, path_maxncpy, "datafiles", subfolder)) {
624 break;
625 }
626 return false;
627
629 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_DATAFILES")) {
630 break;
631 }
632 if (get_path_user(path, path_maxncpy, "datafiles", subfolder)) {
633 break;
634 }
635 return false;
636
638 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
639 break;
640 }
641 if (get_path_system(path, path_maxncpy, "datafiles", subfolder)) {
642 break;
643 }
644 if (get_path_local(path, path_maxncpy, "datafiles", subfolder)) {
645 break;
646 }
647 return false;
648
650 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_CONFIG")) {
651 break;
652 }
653 if (get_path_user(path, path_maxncpy, "config", subfolder)) {
654 break;
655 }
656 return false;
657
659 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_SCRIPTS")) {
660 break;
661 }
662 if (get_path_user(path, path_maxncpy, "scripts", subfolder)) {
663 break;
664 }
665 return false;
666
668 if (get_path_system(path, path_maxncpy, "scripts", subfolder)) {
669 break;
670 }
671 if (get_path_local(path, path_maxncpy, "scripts", subfolder)) {
672 break;
673 }
674 return false;
675
677 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_EXTENSIONS")) {
678 break;
679 }
680 if (get_path_user(path, path_maxncpy, "extensions", subfolder)) {
681 break;
682 }
683 return false;
684
686 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_EXTENSIONS")) {
687 break;
688 }
689 if (get_path_system(path, path_maxncpy, "extensions", subfolder)) {
690 break;
691 }
692 if (get_path_local(path, path_maxncpy, "extensions", subfolder)) {
693 break;
694 }
695 return false;
696
698 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_PYTHON")) {
699 break;
700 }
701 if (get_path_system(path, path_maxncpy, "python", subfolder)) {
702 break;
703 }
704 if (get_path_local(path, path_maxncpy, "python", subfolder)) {
705 break;
706 }
707 return false;
708
709 default:
711 break;
712 }
713
714 return true;
715}
716
717std::optional<std::string> BKE_appdir_folder_id(const int folder_id, const char *subfolder)
718{
719 char path[FILE_MAX] = "";
720 if (BKE_appdir_folder_id_ex(folder_id, subfolder, path, sizeof(path))) {
721 return path;
722 }
723 return std::nullopt;
724}
725
726std::optional<std::string> BKE_appdir_folder_id_user_notest(const int folder_id,
727 const char *subfolder)
728{
729 const int version = BLENDER_VERSION;
730 char path[FILE_MAX] = "";
731 const bool check_is_dir = false;
732
733 switch (folder_id) {
736 path, sizeof(path), subfolder, "BLENDER_USER_DATAFILES", check_is_dir))
737 {
738 break;
739 }
740 get_path_user_ex(path, sizeof(path), "datafiles", subfolder, version, check_is_dir);
741 break;
744 path, sizeof(path), subfolder, "BLENDER_USER_CONFIG", check_is_dir))
745 {
746 break;
747 }
748 get_path_user_ex(path, sizeof(path), "config", subfolder, version, check_is_dir);
749 break;
752 path, sizeof(path), subfolder, "BLENDER_USER_SCRIPTS", check_is_dir))
753 {
754 break;
755 }
756 get_path_user_ex(path, sizeof(path), "scripts", subfolder, version, check_is_dir);
757 break;
760 path, sizeof(path), subfolder, "BLENDER_USER_EXTENSIONS", check_is_dir))
761 {
762 break;
763 }
764 get_path_user_ex(path, sizeof(path), "extensions", subfolder, version, check_is_dir);
765 break;
766 default:
768 break;
769 }
770
771 if ('\0' == path[0]) {
772 return std::nullopt;
773 }
774 return path;
775}
776
777std::optional<std::string> BKE_appdir_folder_id_create(const int folder_id, const char *subfolder)
778{
779 /* Only for user folders. */
780 if (!ELEM(folder_id,
785 {
787 return std::nullopt;
788 }
789
790 std::optional<std::string> path = BKE_appdir_folder_id(folder_id, subfolder);
791
792 if (!path.has_value()) {
793 path = BKE_appdir_folder_id_user_notest(folder_id, subfolder);
794 if (path.has_value()) {
795 BLI_dir_create_recursive(path->c_str());
796 }
797 }
798
799 return path;
800}
801
802std::optional<std::string> BKE_appdir_resource_path_id_with_version(const int folder_id,
803 const bool check_is_dir,
804 const int version)
805{
806 char path[FILE_MAX] = "";
807 bool ok;
808 switch (folder_id) {
810 ok = get_path_user_ex(path, sizeof(path), nullptr, nullptr, version, check_is_dir);
811 break;
813 ok = get_path_local_ex(path, sizeof(path), nullptr, nullptr, version, check_is_dir);
814 break;
816 ok = get_path_system_ex(path, sizeof(path), nullptr, nullptr, version, check_is_dir);
817 break;
818 default:
819 path[0] = '\0'; /* in case check_is_dir is false */
820 ok = false;
821 BLI_assert_msg(0, "incorrect ID");
822 break;
823 }
824 if (!ok) {
825 return std::nullopt;
826 }
827 return path;
828}
829
830std::optional<std::string> BKE_appdir_resource_path_id(const int folder_id,
831 const bool check_is_dir)
832{
833 return BKE_appdir_resource_path_id_with_version(folder_id, check_is_dir, BLENDER_VERSION);
834}
835
837
838/* -------------------------------------------------------------------- */
843
844#ifndef WITH_PYTHON_MODULE
857static void where_am_i(char *program_filepath,
858 const size_t program_filepath_maxncpy,
859 const char *program_name)
860{
861# ifdef WITH_BINRELOC
862 /* Linux uses `binreloc` since `argv[0]` is not reliable, call `br_init(nullptr)` first. */
863 {
864 const char *path = nullptr;
865 path = br_find_exe(nullptr);
866 if (path) {
867 BLI_strncpy(program_filepath, path, program_filepath_maxncpy);
868 free((void *)path);
869 return;
870 }
871 }
872# endif
873
874# ifdef _WIN32
875 {
876 wchar_t *fullname_16 = MEM_malloc_arrayN<wchar_t>(program_filepath_maxncpy, "ProgramPath");
877 if (GetModuleFileNameW(0, fullname_16, program_filepath_maxncpy)) {
878 conv_utf_16_to_8(fullname_16, program_filepath, program_filepath_maxncpy);
881 "path can't be found: \"%.*s\"",
882 int(program_filepath_maxncpy),
884 MessageBox(nullptr,
885 "path contains invalid characters or is too long (see console)",
886 "Error",
887 MB_OK);
888 }
889 MEM_freeN(fullname_16);
890 return;
891 }
892
893 MEM_freeN(fullname_16);
894 }
895# endif
896
897 /* Unix and non Linux. */
898 if (program_name && program_name[0]) {
899
900 BLI_strncpy(program_filepath, program_name, program_filepath_maxncpy);
901 if (program_name[0] == '.') {
902 BLI_path_abs_from_cwd(program_filepath, program_filepath_maxncpy);
903# ifdef _WIN32
904 BLI_path_program_extensions_add_win32(program_filepath, program_filepath_maxncpy);
905# endif
906 }
907 else if (BLI_path_slash_rfind(program_name)) {
908 /* Full path. */
909 BLI_strncpy(program_filepath, program_name, program_filepath_maxncpy);
910# ifdef _WIN32
911 BLI_path_program_extensions_add_win32(program_filepath, program_filepath_maxncpy);
912# endif
913 }
914 else {
915 BLI_path_program_search(program_filepath, program_filepath_maxncpy, program_name);
916 }
917 /* Remove "/./" and "/../" so string comparisons can be used on the path. */
919
920# ifndef NDEBUG
921 if (!STREQ(program_name, program_filepath)) {
922 CLOG_INFO(&LOG, 2, "guessing '%s' == '%s'", program_name, program_filepath);
923 }
924# endif
925 }
926}
927#endif /* WITH_PYTHON_MODULE */
928
929void BKE_appdir_program_path_init(const char *argv0)
930{
931#ifdef WITH_PYTHON_MODULE
932 /* NOTE(@ideasman42): Always use `argv[0]` as is, when building as a Python module.
933 * Otherwise other methods of detecting the binary that override this argument
934 * which must point to the Python module for data-files to be detected. */
935 STRNCPY(g_app.program_filepath, argv0);
936 BLI_path_canonicalize_native(g_app.program_filepath, sizeof(g_app.program_filepath));
937
938 if (g_app.program_dirname[0] == '\0') {
939 /* First time initializing, the file binary path isn't valid from a Python module.
940 * Calling again must set the `filepath` and leave the directory as-is. */
942 g_app.program_filepath, g_app.program_dirname, sizeof(g_app.program_dirname));
943 g_app.program_filepath[0] = '\0';
944 }
945#else
946 where_am_i(g_app.program_filepath, sizeof(g_app.program_filepath), argv0);
948 g_app.program_filepath, g_app.program_dirname, sizeof(g_app.program_dirname));
949#endif
950}
951
953{
954#ifndef WITH_PYTHON_MODULE /* Default's to empty when building as a Python module. */
955 BLI_assert(g_app.program_filepath[0]);
956#endif
957 return g_app.program_filepath;
958}
959
961{
962 BLI_assert(g_app.program_dirname[0]);
963 return g_app.program_dirname;
964}
965
967 const size_t program_filepath_maxncpy,
968 const int version_major,
969 const int version_minor)
970{
972
973#ifdef PYTHON_EXECUTABLE_NAME
974 /* Passed in from the build-systems 'PYTHON_EXECUTABLE'. */
975 const char *python_build_def = STRINGIFY(PYTHON_EXECUTABLE_NAME);
976#endif
977 const char *basename = "python";
978#if defined(WIN32) && !defined(NDEBUG)
979 const char *basename_debug = "python_d";
980#endif
981 char python_version[16];
982 /* Check both possible names. */
983 const char *python_names[] = {
984#ifdef PYTHON_EXECUTABLE_NAME
985 python_build_def,
986#endif
987#if defined(WIN32) && !defined(NDEBUG)
988 basename_debug,
989#endif
990 python_version,
991 basename,
992 };
993 bool is_found = false;
994
995 SNPRINTF(python_version, "%s%d.%d", basename, version_major, version_minor);
996
997 {
998 const std::optional<std::string> python_bin_dir = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON,
999 "bin");
1000 if (python_bin_dir.has_value()) {
1001
1002 for (int i = 0; i < ARRAY_SIZE(python_names); i++) {
1004 program_filepath, program_filepath_maxncpy, python_bin_dir->c_str(), python_names[i]);
1005
1006 if (
1007#ifdef _WIN32
1008 BLI_path_program_extensions_add_win32(program_filepath, program_filepath_maxncpy)
1009#else
1011#endif
1012 )
1013 {
1014 is_found = true;
1015 break;
1016 }
1017 }
1018 }
1019 }
1020
1021 if (is_found == false) {
1022 for (int i = 0; i < ARRAY_SIZE(python_names); i++) {
1023 if (BLI_path_program_search(program_filepath, program_filepath_maxncpy, python_names[i])) {
1024 is_found = true;
1025 break;
1026 }
1027 }
1028 }
1029
1030 if (is_found == false) {
1031 *program_filepath = '\0';
1032 }
1033
1034 return is_found;
1035}
1036
1038
1039/* -------------------------------------------------------------------- */
1042
1044{
1045 blender::Vector<std::string> directories;
1046
1048 char temp_dir[FILE_MAX];
1050 "startup" SEP_STR "bl_app_templates_user",
1051 temp_dir,
1052 sizeof(temp_dir)))
1053 {
1054 directories.append(temp_dir);
1055 }
1056
1057 /* Environment variable. */
1059 "startup" SEP_STR "bl_app_templates_system", "BLENDER_SYSTEM_SCRIPTS", true));
1060
1061 /* Local or system directory. */
1063 "startup" SEP_STR "bl_app_templates_system",
1064 temp_dir,
1065 sizeof(temp_dir)))
1066 {
1067 directories.append(temp_dir);
1068 }
1069
1070 return directories;
1071}
1072
1077
1078bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_maxncpy)
1079{
1081
1082 for (const std::string &directory : directories) {
1083 BLI_path_join(path, path_maxncpy, directory.c_str(), app_template);
1084 if (BLI_is_dir(path)) {
1085 return true;
1086 }
1087 }
1088
1089 return false;
1090}
1091
1093{
1094 /* Test if app template provides a `userpref.blend`.
1095 * If not, we will share user preferences with the rest of Blender. */
1096 if (app_template[0] == '\0') {
1097 return false;
1098 }
1099
1100 char app_template_path[FILE_MAX];
1102 app_template, app_template_path, sizeof(app_template_path)))
1103 {
1104 return false;
1105 }
1106
1107 char userpref_path[FILE_MAX];
1108 BLI_path_join(userpref_path, sizeof(userpref_path), app_template_path, BLENDER_USERPREF_FILE);
1109 return BLI_exists(userpref_path);
1110}
1111
1113{
1114 BLI_listbase_clear(templates);
1115
1117
1118 for (const std::string &subdir : directories) {
1119 direntry *dirs;
1120 const uint dir_num = BLI_filelist_dir_contents(subdir.c_str(), &dirs);
1121 for (int f = 0; f < dir_num; f++) {
1122 if (!FILENAME_IS_CURRPAR(dirs[f].relname) && S_ISDIR(dirs[f].type)) {
1123 char *app_template = BLI_strdup(dirs[f].relname);
1125 }
1126 }
1127
1128 BLI_filelist_free(dirs, dir_num);
1129 }
1130}
1131
1133
1134/* -------------------------------------------------------------------- */
1137
1151static bool where_is_temp(char *tempdir, const size_t tempdir_maxncpy, const char *userdir)
1152{
1153 if (userdir && BLI_temp_directory_path_copy_if_valid(tempdir, tempdir_maxncpy, userdir)) {
1154 return true;
1155 }
1156 BLI_temp_directory_path_get(tempdir, tempdir_maxncpy);
1157 return false;
1158}
1159
1160static bool tempdir_session_create(char *tempdir_session,
1161 const size_t tempdir_session_maxncpy,
1162 const char *tempdir)
1163{
1164 tempdir_session[0] = '\0';
1165
1166 const int tempdir_len = strlen(tempdir);
1167 /* 'XXXXXX' is kind of tag to be replaced by `mktemp-family` by an UUID. */
1168 const char *session_name = "blender_XXXXXX";
1169 const int session_name_len = strlen(session_name);
1170
1171 /* +1 as a slash is added,
1172 * #_mktemp_s also requires the last null character is included. */
1173 const int tempdir_session_len_required = tempdir_len + session_name_len + 1;
1174
1175 if (tempdir_session_len_required <= tempdir_session_maxncpy) {
1176 /* No need to use path joining utility as we know the last character of #tempdir is a slash. */
1177 BLI_string_join(tempdir_session, tempdir_session_maxncpy, tempdir, session_name);
1178#ifdef WIN32
1179 const bool needs_create = (_mktemp_s(tempdir_session, tempdir_session_len_required) == 0);
1180#else
1181 const bool needs_create = (mkdtemp(tempdir_session) == nullptr);
1182#endif
1183 if (needs_create) {
1184 BLI_dir_create_recursive(tempdir_session);
1185 }
1186 if (BLI_is_dir(tempdir_session)) {
1187 BLI_path_slash_ensure(tempdir_session, tempdir_session_maxncpy);
1188 /* Success. */
1189 return true;
1190 }
1191 }
1192
1193 CLOG_WARN(&LOG, "Could not generate a temp file name for '%s'", tempdir_session);
1194 return false;
1195}
1196
1197void BKE_tempdir_init(const char *userdir)
1198{
1199 /* Sets #g_app.temp_dirname_base to `userdir` if specified and is a valid directory,
1200 * otherwise chooses a suitable OS-specific temporary directory.
1201 * Sets #g_app.temp_dirname_session to a #mkdtemp
1202 * generated sub-dir of #g_app.temp_dirname_base. */
1203
1204 /* Clear existing temp dir, if needed. */
1206
1207 /* Perform two passes, the first pass for the user preference path,
1208 * then a second pass if the the preferences failed to create the *session* sub-directory.
1209 *
1210 * This avoid problems if the preferences points to a path without write access,
1211 * `C:\` or `/` for example. */
1212 g_app.temp_dirname_session_can_be_deleted = false;
1213 for (int pass = 0; pass < 2; pass += 1) {
1214 const bool from_userdir = where_is_temp(
1215 g_app.temp_dirname_base, sizeof(g_app.temp_dirname_base), pass == 0 ? userdir : nullptr);
1216
1217 /* Now that we have a valid temp dir, add system-generated unique sub-dir. */
1218 if (tempdir_session_create(g_app.temp_dirname_session,
1219 sizeof(g_app.temp_dirname_session),
1220 g_app.temp_dirname_base))
1221 {
1222 /* Created the session sub-directory. */
1223 g_app.temp_dirname_session_can_be_deleted = true;
1224 break;
1225 }
1226
1227 /* Only perform the second pass if the `userdir` was used
1228 * and failed to created the sub-directory. */
1229 if (from_userdir == false) {
1230 break;
1231 }
1232 }
1233
1234 if (UNLIKELY(g_app.temp_dirname_session_can_be_deleted == false)) {
1235 /* This should practically never happen as either the preferences or the systems
1236 * default temporary directory should be usable, if not, use the base directory and warn. */
1237 STRNCPY(g_app.temp_dirname_session, g_app.temp_dirname_base);
1238 CLOG_WARN(&LOG,
1239 "Could not generate a temp session subdirectory, falling back to '%s'",
1240 g_app.temp_dirname_base);
1241 }
1242}
1243
1245{
1246 return g_app.temp_dirname_session[0] ? g_app.temp_dirname_session : BKE_tempdir_base();
1247}
1248
1249const char *BKE_tempdir_base()
1250{
1251 return g_app.temp_dirname_base;
1252}
1253
1255{
1256 if (g_app.temp_dirname_session_can_be_deleted == false) {
1257 /* It's possible this path references an arbitrary location
1258 * in that case *never* recursively remove, see: #139585. */
1259 return;
1260 }
1261 if (g_app.temp_dirname_session[0] && BLI_is_dir(g_app.temp_dirname_session)) {
1262 BLI_delete(g_app.temp_dirname_session, true, true);
1263 }
1264}
1265
@ BLENDER_USER_DATAFILES
@ BLENDER_USER_EXTENSIONS
@ BLENDER_SYSTEM_DATAFILES
@ BLENDER_SYSTEM_EXTENSIONS
@ BLENDER_DATAFILES
@ BLENDER_SYSTEM_PYTHON
@ BLENDER_SYSTEM_SCRIPTS
@ BLENDER_USER_CONFIG
@ BLENDER_USER_SCRIPTS
#define BLENDER_USERPREF_FILE
@ BLENDER_RESOURCE_PATH_SYSTEM
@ BLENDER_RESOURCE_PATH_LOCAL
@ BLENDER_RESOURCE_PATH_USER
#define BLENDER_VERSION
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
File and directory operations.
int BLI_exists(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:373
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:391
unsigned int BLI_filelist_dir_contents(const char *dirname, struct direntry **r_filelist)
int BLI_delete(const char *path, bool dir, bool recursive) ATTR_NONNULL()
bool BLI_is_dir(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL()
Definition storage.cc:456
void BLI_filelist_free(struct direntry *filelist, unsigned int nrentries)
#define PATH_MAX
Definition BLI_fileops.h:26
const char * BLI_dir_home(void)
Definition storage.cc:110
Some types for dealing with directories.
void BLI_kdtree_nd_ free(KDTree *tree)
LinkData * BLI_genericNodeN(void *data)
Definition listbase.cc:922
BLI_INLINE void BLI_listbase_clear(ListBase *lb)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
#define FILE_MAX
bool BLI_path_program_search(char *program_filepath, size_t program_filepath_maxncpy, const char *program_name) ATTR_NONNULL(1
#define BLI_path_join(...)
const char * BLI_getenv(const char *env) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
#define FILENAME_IS_CURRPAR(_n)
int BLI_path_canonicalize_native(char *path, int path_maxncpy)
void void BLI_path_split_dir_part(const char *filepath, char *dir, size_t dir_maxncpy) ATTR_NONNULL(1
size_t BLI_path_join_array(char *__restrict dst, const size_t dst_maxncpy, const char *path_array[], const int path_array_num) ATTR_NONNULL(1
int BLI_path_slash_ensure(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
bool BLI_path_abs_from_cwd(char *path, size_t path_maxncpy) ATTR_NONNULL(1)
const char * BLI_path_slash_rfind(const char *path) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
int BLI_path_normalize_native(char *path) ATTR_NONNULL(1)
#define FILE_MAXDIR
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
char char size_t char const char * BLI_strchr_or_end(const char *str, char ch) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL ATTR_NONNULL(1)
Definition string.cc:941
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:599
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:688
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
size_t BLI_strncpy_wchar_as_utf8(char *__restrict dst, const wchar_t *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define BLI_string_join(...)
unsigned int uint
bool void BLI_temp_directory_path_get(char *tempdir, const size_t tempdir_maxncpy) ATTR_NONNULL(1)
Definition tempfile.cc:57
bool BLI_temp_directory_path_copy_if_valid(char *tempdir, const size_t tempdir_maxncpy, const char *dirpath) ATTR_NONNULL(1
#define ARRAY_SIZE(arr)
#define STRINGIFY(x)
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
Compatibility-like things for windows.
void BLI_windows_get_default_root_dir(char root_dir[4])
#define S_ISDIR(x)
#define CLOG_ERROR(clg_ref,...)
Definition CLG_log.h:182
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:181
#define CLOG_INFO(clg_ref, level,...)
Definition CLG_log.h:179
const char * GHOST_getUserSpecialDir(GHOST_TUserSpecialDirTypes type)
const char * GHOST_getSystemDir(int version, const char *versionstr)
GHOST_TSuccess GHOST_DisposeSystemPaths()
const char * GHOST_getUserDir(int version, const char *versionstr)
@ GHOST_kUserSpecialDirCaches
@ GHOST_kUserSpecialDirDocuments
#define ASSERT_IS_INIT()
Definition appdir.cc:88
static const char _str_null[]
Definition appdir.cc:54
void BKE_tempdir_init(const char *userdir)
Definition appdir.cc:1197
static bool is_appdir_init
Definition appdir.cc:87
void BKE_appdir_init()
Definition appdir.cc:93
std::optional< std::string > BKE_appdir_folder_id_create(const int folder_id, const char *subfolder)
Definition appdir.cc:777
static struct @004337376000312322276010372351321061214207011145 g_app
char program_dirname[FILE_MAX]
Definition appdir.cc:68
static bool get_path_user_ex(char *targetpath, size_t targetpath_maxncpy, const char *folder_name, const char *subfolder_name, const int version, const bool check_is_dir)
Definition appdir.cc:489
static bool get_path_environment(char *targetpath, size_t targetpath_maxncpy, const char *subfolder_name, const char *envvar)
Definition appdir.cc:432
std::optional< std::string > BKE_appdir_resource_path_id(const int folder_id, const bool check_is_dir)
Definition appdir.cc:830
const char * BKE_appdir_folder_root()
Definition appdir.cc:152
const char * BKE_tempdir_session()
Definition appdir.cc:1244
char temp_dirname_session[FILE_MAX]
Definition appdir.cc:72
std::optional< std::string > BKE_appdir_folder_id_user_notest(const int folder_id, const char *subfolder)
Definition appdir.cc:726
bool BKE_appdir_folder_documents(char *dir)
Definition appdir.cc:172
static bool test_env_path(char *path, const char *envvar, const bool check_is_dir)
Definition appdir.cc:312
bool BKE_appdir_app_template_has_userpref(const char *app_template)
Definition appdir.cc:1092
const char * BKE_appdir_program_dir()
Definition appdir.cc:960
void BKE_appdir_exit()
Definition appdir.cc:101
static bool test_path(char *targetpath, size_t targetpath_maxncpy, const bool check_is_dir, const char *path_base, const char *folder_name, const char *subfolder_name)
Definition appdir.cc:272
bool BKE_appdir_program_python_search(char *program_filepath, const size_t program_filepath_maxncpy, const int version_major, const int version_minor)
Definition appdir.cc:966
static bool get_path_system_ex(char *targetpath, size_t targetpath_maxncpy, const char *folder_name, const char *subfolder_name, const int version, const bool check_is_dir)
Definition appdir.cc:551
void BKE_appdir_program_path_init(const char *argv0)
Definition appdir.cc:929
static bool where_is_temp(char *tempdir, const size_t tempdir_maxncpy, const char *userdir)
Definition appdir.cc:1151
std::optional< std::string > BKE_appdir_resource_path_id_with_version(const int folder_id, const bool check_is_dir, const int version)
Definition appdir.cc:802
const char * BKE_appdir_folder_default_or_root()
Definition appdir.cc:163
bool BKE_appdir_folder_id_ex(const int folder_id, const char *subfolder, char *path, size_t path_maxncpy)
Definition appdir.cc:604
static bool tempdir_session_create(char *tempdir_session, const size_t tempdir_session_maxncpy, const char *tempdir)
Definition appdir.cc:1160
static char * blender_version_decimal(const int version)
Definition appdir.cc:123
static blender::Vector< std::string > appdir_app_template_directories()
Definition appdir.cc:1043
#define STR_OR_FALLBACK(a)
Definition appdir.cc:55
static bool get_path_local(char *targetpath, size_t targetpath_maxncpy, const char *folder_name, const char *subfolder_name)
Definition appdir.cc:397
static blender::Vector< std::string > get_path_environment_multiple(const char *subfolder_name, const char *envvar, const bool check_is_dir)
Definition appdir.cc:442
bool temp_dirname_session_can_be_deleted
Definition appdir.cc:77
std::optional< std::string > BKE_appdir_folder_id(const int folder_id, const char *subfolder)
Definition appdir.cc:717
bool BKE_appdir_app_template_any()
Definition appdir.cc:1073
void BKE_appdir_app_templates(ListBase *templates)
Definition appdir.cc:1112
bool BKE_appdir_font_folder_default(char *dir, size_t dir_maxncpy)
Definition appdir.cc:226
bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_maxncpy)
Definition appdir.cc:1078
const char * BKE_appdir_folder_default()
Definition appdir.cc:137
static bool get_path_local_ex(char *targetpath, size_t targetpath_maxncpy, const char *folder_name, const char *subfolder_name, const int version, const bool check_is_dir)
Definition appdir.cc:353
const char * BKE_appdir_program_path()
Definition appdir.cc:952
char temp_dirname_base[FILE_MAX]
Definition appdir.cc:70
void BKE_tempdir_session_purge()
Definition appdir.cc:1254
static void where_am_i(char *program_filepath, const size_t program_filepath_maxncpy, const char *program_name)
Definition appdir.cc:857
static bool get_path_system(char *targetpath, size_t targetpath_maxncpy, const char *folder_name, const char *subfolder_name)
Definition appdir.cc:587
static bool get_path_user(char *targetpath, size_t targetpath_maxncpy, const char *folder_name, const char *subfolder_name)
Definition appdir.cc:530
char program_filepath[FILE_MAX]
Definition appdir.cc:66
const char * BKE_tempdir_base()
Definition appdir.cc:1249
static bool get_path_environment_ex(char *targetpath, size_t targetpath_maxncpy, const char *subfolder_name, const char *envvar, const bool check_is_dir)
Definition appdir.cc:417
bool BKE_appdir_folder_caches(char *path, const size_t path_maxncpy)
Definition appdir.cc:202
bool is_empty() const
void append(const T &value)
void extend(Span< T > array)
#define LOG(severity)
Definition log.h:32
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
i
Definition text_draw.cc:230
#define SEP_STR
Definition unit.cc:39
int conv_utf_16_to_8(const wchar_t *in16, char *out8, size_t size8)
Definition utfconv.cc:116
#define N_(msgid)
char app_template[64]
Definition wm_files.cc:1183