Blender V5.0
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 = {"system.path"};
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 std::optional<std::string> documents_path = GHOST_getUserSpecialDir(
178
179 /* Usual case: Ghost gave us the documents path. We're done here. */
180 if (documents_path && BLI_is_dir(documents_path->c_str())) {
181 BLI_strncpy(dir, documents_path->c_str(), FILE_MAXDIR);
182 return true;
183 }
184
185 /* Ghost couldn't give us a documents path, let's try if we can find it ourselves. */
186
187 const char *home_path = BLI_dir_home();
188 if (!home_path || !BLI_is_dir(home_path)) {
189 return false;
190 }
191
192 char try_documents_path[FILE_MAXDIR];
193 /* Own attempt at getting a valid Documents path. */
194 BLI_path_join(try_documents_path, sizeof(try_documents_path), home_path, N_("Documents"));
195 if (!BLI_is_dir(try_documents_path)) {
196 return false;
197 }
198
199 BLI_strncpy(dir, try_documents_path, FILE_MAXDIR);
200 return true;
201}
202
203bool BKE_appdir_folder_caches(char *path, const size_t path_maxncpy)
204{
205 path[0] = '\0';
206
207 std::optional<std::string> caches_root_path = GHOST_getUserSpecialDir(
209 if (!caches_root_path || !BLI_is_dir(caches_root_path->c_str())) {
210 caches_root_path = BKE_tempdir_base();
211 }
212 if (!caches_root_path || !BLI_is_dir(caches_root_path->c_str())) {
213 return false;
214 }
215
216#ifdef WIN32
217 BLI_path_join(path,
218 path_maxncpy,
219 caches_root_path->c_str(),
220 "Blender Foundation",
221 "Blender",
222 "Cache",
223 SEP_STR);
224#elif defined(__APPLE__)
225 BLI_path_join(path, path_maxncpy, caches_root_path->c_str(), "Blender", SEP_STR);
226#else /* __linux__ */
227 BLI_path_join(path, path_maxncpy, caches_root_path->c_str(), "blender", SEP_STR);
228#endif
229
230 return true;
231}
232
233bool BKE_appdir_font_folder_default(char *dir, size_t dir_maxncpy)
234{
235 char test_dir[FILE_MAXDIR];
236 test_dir[0] = '\0';
237
238#ifdef WIN32
239 wchar_t wpath[MAX_PATH];
240 if (SHGetSpecialFolderPathW(0, wpath, CSIDL_FONTS, 0)) {
241 BLI_strncpy_wchar_as_utf8(test_dir, wpath, sizeof(test_dir));
242 }
243#elif defined(__APPLE__)
244 if (const char *home_dir = BLI_dir_home()) {
245 BLI_path_join(test_dir, sizeof(test_dir), home_dir, "Library/Fonts");
246 }
247#else
248 STRNCPY(test_dir, "/usr/share/fonts");
249#endif
250
251 if (test_dir[0] && BLI_exists(test_dir)) {
252 BLI_strncpy(dir, test_dir, dir_maxncpy);
253 return true;
254 }
255 return false;
256}
257
259
260/* -------------------------------------------------------------------- */
263
279static bool test_path(char *targetpath,
280 size_t targetpath_maxncpy,
281 const bool check_is_dir,
282 const char *path_base,
283 const char *folder_name,
284 const char *subfolder_name)
285{
287
288 /* Only the last argument should be nullptr. */
289 BLI_assert(!(folder_name == nullptr && (subfolder_name != nullptr)));
290 const char *path_array[] = {path_base, folder_name, subfolder_name};
291 const int path_array_num = (folder_name ? (subfolder_name ? 3 : 2) : 1);
292 BLI_path_join_array(targetpath, targetpath_maxncpy, path_array, path_array_num);
293 if (check_is_dir == false) {
294 CLOG_DEBUG(&LOG, "Using (without test): '%s'", targetpath);
295 return true;
296 }
297
298 if (BLI_is_dir(targetpath)) {
299 CLOG_DEBUG(&LOG, "Found '%s'", targetpath);
300 return true;
301 }
302
303 CLOG_DEBUG(&LOG, "Missing '%s'", targetpath);
304
305 /* Path not found, don't accidentally use it,
306 * otherwise call this function with `check_is_dir` set to false. */
307 targetpath[0] = '\0';
308 return false;
309}
310
319static bool test_env_path(char *path, const char *envvar, const bool check_is_dir)
320{
322
323 const char *env_path = envvar ? BLI_getenv(envvar) : nullptr;
324 if (!env_path) {
325 return false;
326 }
327
328 BLI_strncpy(path, env_path, FILE_MAX);
329
330 if (check_is_dir == false) {
331 CLOG_DEBUG(&LOG, "Using env '%s' (without test): '%s'", envvar, env_path);
332 return true;
333 }
334
335 if (BLI_is_dir(env_path)) {
336 CLOG_DEBUG(&LOG, "Env '%s' found: %s", envvar, env_path);
337 return true;
338 }
339
340 CLOG_DEBUG(&LOG, "Env '%s' missing: %s", envvar, env_path);
341
342 /* Path not found, don't accidentally use it,
343 * otherwise call this function with `check_is_dir` set to false. */
344 path[0] = '\0';
345 return false;
346}
347
360static bool get_path_local_ex(char *targetpath,
361 size_t targetpath_maxncpy,
362 const char *folder_name,
363 const char *subfolder_name,
364 const int version,
365 const bool check_is_dir)
366{
367 char relfolder[FILE_MAX];
368
370 "Get path local: folder='%s', subfolder='%s'",
371 STR_OR_FALLBACK(folder_name),
372 STR_OR_FALLBACK(subfolder_name));
373
374 if (folder_name) { /* `subfolder_name` may be nullptr. */
375 const char *path_array[] = {folder_name, subfolder_name};
376 const int path_array_num = subfolder_name ? 2 : 1;
377 BLI_path_join_array(relfolder, sizeof(relfolder), path_array, path_array_num);
378 }
379 else {
380 relfolder[0] = '\0';
381 }
382
383 /* Try `{g_app.program_dirname}/3.xx/{folder_name}` the default directory
384 * for a portable distribution. See `WITH_INSTALL_PORTABLE` build-option. */
385 const char *path_base = g_app.program_dirname;
386#if defined(__APPLE__) && !defined(WITH_PYTHON_MODULE)
387 /* Due new code-sign situation in OSX > 10.9.5
388 * we must move the blender_version dir with contents to Resources.
389 * Add 4 + 9 for the temporary `/../` path & `Resources`. */
390 char osx_resourses[FILE_MAX + 4 + 9];
391 BLI_path_join(osx_resourses, sizeof(osx_resourses), g_app.program_dirname, "..", "Resources");
392 /* Remove the '/../' added above. */
393 BLI_path_normalize_native(osx_resourses);
394 path_base = osx_resourses;
395#endif
396 return test_path(targetpath,
397 targetpath_maxncpy,
398 check_is_dir,
399 path_base,
400 (version) ? blender_version_decimal(version) : relfolder,
401 (version) ? relfolder : nullptr);
402}
403static bool get_path_local(char *targetpath,
404 size_t targetpath_maxncpy,
405 const char *folder_name,
406 const char *subfolder_name)
407{
408 const int version = BLENDER_VERSION;
409 const bool check_is_dir = true;
410 return get_path_local_ex(
411 targetpath, targetpath_maxncpy, folder_name, subfolder_name, version, check_is_dir);
412}
413
423static bool get_path_environment_ex(char *targetpath,
424 size_t targetpath_maxncpy,
425 const char *subfolder_name,
426 const char *envvar,
427 const bool check_is_dir)
428{
429 char user_path[FILE_MAX];
430
431 if (test_env_path(user_path, envvar, check_is_dir)) {
432 /* Note that `subfolder_name` may be nullptr, in this case we use `user_path` as-is. */
433 return test_path(
434 targetpath, targetpath_maxncpy, check_is_dir, user_path, subfolder_name, nullptr);
435 }
436 return false;
437}
438static bool get_path_environment(char *targetpath,
439 size_t targetpath_maxncpy,
440 const char *subfolder_name,
441 const char *envvar)
442{
443 const bool check_is_dir = true;
445 targetpath, targetpath_maxncpy, subfolder_name, envvar, check_is_dir);
446}
447
449 const char *envvar,
450 const bool check_is_dir)
451{
453 const char *env_path = envvar ? BLI_getenv(envvar) : nullptr;
454 if (!env_path) {
455 return paths;
456 }
457
458#ifdef _WIN32
459 const char separator = ';';
460#else
461 const char separator = ':';
462#endif
463
464 const char *char_begin = env_path;
465 const char *char_end = BLI_strchr_or_end(char_begin, separator);
466 while (char_begin[0]) {
467 const size_t base_path_len = char_end - char_begin;
468 if (base_path_len > 0 && base_path_len < PATH_MAX) {
469 char base_path[PATH_MAX];
470 memcpy(base_path, char_begin, base_path_len);
471 base_path[base_path_len] = '\0';
472
473 char path[PATH_MAX];
474 if (test_path(path, sizeof(path), check_is_dir, base_path, subfolder_name, nullptr)) {
475 paths.append(path);
476 }
477 }
478 char_begin = char_end[0] ? char_end + 1 : char_end;
479 char_end = BLI_strchr_or_end(char_begin, separator);
480 }
481
482 return paths;
483}
484
495static bool get_path_user_ex(char *targetpath,
496 size_t targetpath_maxncpy,
497 const char *folder_name,
498 const char *subfolder_name,
499 const int version,
500 const bool check_is_dir)
501{
502 char user_path[FILE_MAX];
503
504 /* Environment variable override. */
505 if (test_env_path(user_path, "BLENDER_USER_RESOURCES", check_is_dir)) {
506 /* Pass. */
507 }
508 /* Portable install, to store user files next to Blender executable. */
509 else if (get_path_local_ex(user_path, sizeof(user_path), "portable", nullptr, 0, true)) {
510 /* Pass. */
511 }
512 else {
513 user_path[0] = '\0';
514
515 const char *user_base_path = GHOST_getUserDir(version, blender_version_decimal(version));
516 if (user_base_path) {
517 STRNCPY(user_path, user_base_path);
518 }
519 }
520
521 if (!user_path[0]) {
522 return false;
523 }
524
526 "Get path user: '%s', folder='%s', subfolder='%s'",
527 user_path,
528 STR_OR_FALLBACK(folder_name),
529 STR_OR_FALLBACK(subfolder_name));
530
531 /* `subfolder_name` may be nullptr. */
532 return test_path(
533 targetpath, targetpath_maxncpy, check_is_dir, user_path, folder_name, subfolder_name);
534}
535static bool get_path_user(char *targetpath,
536 size_t targetpath_maxncpy,
537 const char *folder_name,
538 const char *subfolder_name)
539{
540 const int version = BLENDER_VERSION;
541 const bool check_is_dir = true;
542 return get_path_user_ex(
543 targetpath, targetpath_maxncpy, folder_name, subfolder_name, version, check_is_dir);
544}
545
556static bool get_path_system_ex(char *targetpath,
557 size_t targetpath_maxncpy,
558 const char *folder_name,
559 const char *subfolder_name,
560 const int version,
561 const bool check_is_dir)
562{
563 char system_path[FILE_MAX];
564
565 if (test_env_path(system_path, "BLENDER_SYSTEM_RESOURCES", check_is_dir)) {
566 /* Pass. */
567 }
568 else {
569 system_path[0] = '\0';
570 const char *system_base_path = GHOST_getSystemDir(version, blender_version_decimal(version));
571 if (system_base_path) {
572 STRNCPY(system_path, system_base_path);
573 }
574 }
575
576 if (!system_path[0]) {
577 return false;
578 }
579
581 "Get path system: '%s', folder='%s', subfolder='%s'",
582 system_path,
583 STR_OR_FALLBACK(folder_name),
584 STR_OR_FALLBACK(subfolder_name));
585
586 /* Try `$BLENDERPATH/folder_name/subfolder_name`, `subfolder_name` may be nullptr. */
587 return test_path(
588 targetpath, targetpath_maxncpy, check_is_dir, system_path, folder_name, subfolder_name);
589}
590
591static bool get_path_system(char *targetpath,
592 size_t targetpath_maxncpy,
593 const char *folder_name,
594 const char *subfolder_name)
595{
596 const int version = BLENDER_VERSION;
597 const bool check_is_dir = true;
598 return get_path_system_ex(
599 targetpath, targetpath_maxncpy, folder_name, subfolder_name, version, check_is_dir);
600}
601
603
604/* -------------------------------------------------------------------- */
607
608bool BKE_appdir_folder_id_ex(const int folder_id,
609 const char *subfolder,
610 char *path,
611 size_t path_maxncpy)
612{
613 switch (folder_id) {
614 case BLENDER_DATAFILES: /* general case */
615 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_DATAFILES")) {
616 break;
617 }
618 if (get_path_user(path, path_maxncpy, "datafiles", subfolder)) {
619 break;
620 }
621 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
622 break;
623 }
624 if (get_path_system(path, path_maxncpy, "datafiles", subfolder)) {
625 break;
626 }
627 if (get_path_local(path, path_maxncpy, "datafiles", subfolder)) {
628 break;
629 }
630 return false;
631
633 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_DATAFILES")) {
634 break;
635 }
636 if (get_path_user(path, path_maxncpy, "datafiles", subfolder)) {
637 break;
638 }
639 return false;
640
642 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_DATAFILES")) {
643 break;
644 }
645 if (get_path_system(path, path_maxncpy, "datafiles", subfolder)) {
646 break;
647 }
648 if (get_path_local(path, path_maxncpy, "datafiles", subfolder)) {
649 break;
650 }
651 return false;
652
654 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_CONFIG")) {
655 break;
656 }
657 if (get_path_user(path, path_maxncpy, "config", subfolder)) {
658 break;
659 }
660 return false;
661
663 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_SCRIPTS")) {
664 break;
665 }
666 if (get_path_user(path, path_maxncpy, "scripts", subfolder)) {
667 break;
668 }
669 return false;
670
672 if (get_path_system(path, path_maxncpy, "scripts", subfolder)) {
673 break;
674 }
675 if (get_path_local(path, path_maxncpy, "scripts", subfolder)) {
676 break;
677 }
678 return false;
679
681 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_USER_EXTENSIONS")) {
682 break;
683 }
684 if (get_path_user(path, path_maxncpy, "extensions", subfolder)) {
685 break;
686 }
687 return false;
688
690 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_EXTENSIONS")) {
691 break;
692 }
693 if (get_path_system(path, path_maxncpy, "extensions", subfolder)) {
694 break;
695 }
696 if (get_path_local(path, path_maxncpy, "extensions", subfolder)) {
697 break;
698 }
699 return false;
700
702 if (get_path_environment(path, path_maxncpy, subfolder, "BLENDER_SYSTEM_PYTHON")) {
703 break;
704 }
705 if (get_path_system(path, path_maxncpy, "python", subfolder)) {
706 break;
707 }
708 if (get_path_local(path, path_maxncpy, "python", subfolder)) {
709 break;
710 }
711 return false;
712
713 default:
715 break;
716 }
717
718 return true;
719}
720
721std::optional<std::string> BKE_appdir_folder_id(const int folder_id, const char *subfolder)
722{
723 char path[FILE_MAX] = "";
724 if (BKE_appdir_folder_id_ex(folder_id, subfolder, path, sizeof(path))) {
725 return path;
726 }
727 return std::nullopt;
728}
729
730std::optional<std::string> BKE_appdir_folder_id_user_notest(const int folder_id,
731 const char *subfolder)
732{
733 const int version = BLENDER_VERSION;
734 char path[FILE_MAX] = "";
735 const bool check_is_dir = false;
736
737 switch (folder_id) {
740 path, sizeof(path), subfolder, "BLENDER_USER_DATAFILES", check_is_dir))
741 {
742 break;
743 }
744 get_path_user_ex(path, sizeof(path), "datafiles", subfolder, version, check_is_dir);
745 break;
748 path, sizeof(path), subfolder, "BLENDER_USER_CONFIG", check_is_dir))
749 {
750 break;
751 }
752 get_path_user_ex(path, sizeof(path), "config", subfolder, version, check_is_dir);
753 break;
756 path, sizeof(path), subfolder, "BLENDER_USER_SCRIPTS", check_is_dir))
757 {
758 break;
759 }
760 get_path_user_ex(path, sizeof(path), "scripts", subfolder, version, check_is_dir);
761 break;
764 path, sizeof(path), subfolder, "BLENDER_USER_EXTENSIONS", check_is_dir))
765 {
766 break;
767 }
768 get_path_user_ex(path, sizeof(path), "extensions", subfolder, version, check_is_dir);
769 break;
770 default:
772 break;
773 }
774
775 if ('\0' == path[0]) {
776 return std::nullopt;
777 }
778 return path;
779}
780
781std::optional<std::string> BKE_appdir_folder_id_create(const int folder_id, const char *subfolder)
782{
783 /* Only for user folders. */
784 if (!ELEM(folder_id,
789 {
791 return std::nullopt;
792 }
793
794 std::optional<std::string> path = BKE_appdir_folder_id(folder_id, subfolder);
795
796 if (!path.has_value()) {
797 path = BKE_appdir_folder_id_user_notest(folder_id, subfolder);
798 if (path.has_value()) {
799 BLI_dir_create_recursive(path->c_str());
800 }
801 }
802
803 return path;
804}
805
806std::optional<std::string> BKE_appdir_resource_path_id_with_version(const int folder_id,
807 const bool check_is_dir,
808 const int version)
809{
810 char path[FILE_MAX] = "";
811 bool ok;
812 switch (folder_id) {
814 ok = get_path_user_ex(path, sizeof(path), nullptr, nullptr, version, check_is_dir);
815 break;
817 ok = get_path_local_ex(path, sizeof(path), nullptr, nullptr, version, check_is_dir);
818 break;
820 ok = get_path_system_ex(path, sizeof(path), nullptr, nullptr, version, check_is_dir);
821 break;
822 default:
823 path[0] = '\0'; /* in case check_is_dir is false */
824 ok = false;
825 BLI_assert_msg(0, "incorrect ID");
826 break;
827 }
828 if (!ok) {
829 return std::nullopt;
830 }
831 return path;
832}
833
834std::optional<std::string> BKE_appdir_resource_path_id(const int folder_id,
835 const bool check_is_dir)
836{
837 return BKE_appdir_resource_path_id_with_version(folder_id, check_is_dir, BLENDER_VERSION);
838}
839
841
842/* -------------------------------------------------------------------- */
847
848#ifndef WITH_PYTHON_MODULE
861static void where_am_i(char *program_filepath,
862 const size_t program_filepath_maxncpy,
863 const char *program_name)
864{
865# ifdef WITH_BINRELOC
866 /* Linux uses `binreloc` since `argv[0]` is not reliable, call `br_init(nullptr)` first. */
867 {
868 const char *path = nullptr;
869 path = br_find_exe(nullptr);
870 if (path) {
871 BLI_strncpy(program_filepath, path, program_filepath_maxncpy);
872 free((void *)path);
873 return;
874 }
875 }
876# endif
877
878# ifdef _WIN32
879 {
880 wchar_t *fullname_16 = MEM_malloc_arrayN<wchar_t>(program_filepath_maxncpy, "ProgramPath");
881 if (GetModuleFileNameW(0, fullname_16, program_filepath_maxncpy)) {
882 conv_utf_16_to_8(fullname_16, program_filepath, program_filepath_maxncpy);
885 "Program path can't be found: \"%.*s\"",
886 int(program_filepath_maxncpy),
888 MessageBox(nullptr,
889 "path contains invalid characters or is too long (see console)",
890 "Error",
891 MB_OK);
892 }
893 MEM_freeN(fullname_16);
894 return;
895 }
896
897 MEM_freeN(fullname_16);
898 }
899# endif
900
901 /* Unix and non Linux. */
902 if (program_name && program_name[0]) {
903
904 BLI_strncpy(program_filepath, program_name, program_filepath_maxncpy);
905 if (program_name[0] == '.') {
906 BLI_path_abs_from_cwd(program_filepath, program_filepath_maxncpy);
907# ifdef _WIN32
908 BLI_path_program_extensions_add_win32(program_filepath, program_filepath_maxncpy);
909# endif
910 }
911 else if (BLI_path_slash_rfind(program_name)) {
912 /* Full path. */
913 BLI_strncpy(program_filepath, program_name, program_filepath_maxncpy);
914# ifdef _WIN32
915 BLI_path_program_extensions_add_win32(program_filepath, program_filepath_maxncpy);
916# endif
917 }
918 else {
919 BLI_path_program_search(program_filepath, program_filepath_maxncpy, program_name);
920 }
921 /* Remove "/./" and "/../" so string comparisons can be used on the path. */
923
924# ifndef NDEBUG
925 if (!STREQ(program_name, program_filepath)) {
926 CLOG_DEBUG(&LOG, "Program path guessing '%s' == '%s'", program_name, program_filepath);
927 }
928# endif
929 }
930}
931#endif /* WITH_PYTHON_MODULE */
932
933void BKE_appdir_program_path_init(const char *argv0)
934{
935#ifdef WITH_PYTHON_MODULE
936 /* NOTE(@ideasman42): Always use `argv[0]` as is, when building as a Python module.
937 * Otherwise other methods of detecting the binary that override this argument
938 * which must point to the Python module for data-files to be detected. */
939 STRNCPY(g_app.program_filepath, argv0);
940 BLI_path_canonicalize_native(g_app.program_filepath, sizeof(g_app.program_filepath));
941
942 if (g_app.program_dirname[0] == '\0') {
943 /* First time initializing, the file binary path isn't valid from a Python module.
944 * Calling again must set the `filepath` and leave the directory as-is. */
946 g_app.program_filepath, g_app.program_dirname, sizeof(g_app.program_dirname));
947 g_app.program_filepath[0] = '\0';
948 }
949#else
950 where_am_i(g_app.program_filepath, sizeof(g_app.program_filepath), argv0);
952 g_app.program_filepath, g_app.program_dirname, sizeof(g_app.program_dirname));
953#endif
954}
955
957{
958#ifndef WITH_PYTHON_MODULE /* Default's to empty when building as a Python module. */
959 BLI_assert(g_app.program_filepath[0]);
960#endif
961 return g_app.program_filepath;
962}
963
965{
966 BLI_assert(g_app.program_dirname[0]);
967 return g_app.program_dirname;
968}
969
971 const size_t program_filepath_maxncpy,
972 const int version_major,
973 const int version_minor)
974{
976
977#ifdef PYTHON_EXECUTABLE_NAME
978 /* Passed in from the build-systems 'PYTHON_EXECUTABLE'. */
979 const char *python_build_def = STRINGIFY(PYTHON_EXECUTABLE_NAME);
980#endif
981 const char *basename = "python";
982#if defined(WIN32) && !defined(NDEBUG)
983 const char *basename_debug = "python_d";
984#endif
985 char python_version[16];
986 /* Check both possible names. */
987 const char *python_names[] = {
988#ifdef PYTHON_EXECUTABLE_NAME
989 python_build_def,
990#endif
991#if defined(WIN32) && !defined(NDEBUG)
992 basename_debug,
993#endif
994 python_version,
995 basename,
996 };
997 bool is_found = false;
998
999 SNPRINTF(python_version, "%s%d.%d", basename, version_major, version_minor);
1000
1001 {
1002 const std::optional<std::string> python_bin_dir = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON,
1003 "bin");
1004 if (python_bin_dir.has_value()) {
1005
1006 for (int i = 0; i < ARRAY_SIZE(python_names); i++) {
1008 program_filepath, program_filepath_maxncpy, python_bin_dir->c_str(), python_names[i]);
1009
1010 if (
1011#ifdef _WIN32
1012 BLI_path_program_extensions_add_win32(program_filepath, program_filepath_maxncpy)
1013#else
1015#endif
1016 )
1017 {
1018 is_found = true;
1019 break;
1020 }
1021 }
1022 }
1023 }
1024
1025 if (is_found == false) {
1026 for (int i = 0; i < ARRAY_SIZE(python_names); i++) {
1027 if (BLI_path_program_search(program_filepath, program_filepath_maxncpy, python_names[i])) {
1028 is_found = true;
1029 break;
1030 }
1031 }
1032 }
1033
1034 if (is_found == false) {
1035 *program_filepath = '\0';
1036 }
1037
1038 return is_found;
1039}
1040
1042
1043/* -------------------------------------------------------------------- */
1046
1048{
1049 blender::Vector<std::string> directories;
1050
1052 char temp_dir[FILE_MAX];
1054 "startup" SEP_STR "bl_app_templates_user",
1055 temp_dir,
1056 sizeof(temp_dir)))
1057 {
1058 directories.append(temp_dir);
1059 }
1060
1061 /* Environment variable. */
1063 "startup" SEP_STR "bl_app_templates_system", "BLENDER_SYSTEM_SCRIPTS", true));
1064
1065 /* Local or system directory. */
1067 "startup" SEP_STR "bl_app_templates_system",
1068 temp_dir,
1069 sizeof(temp_dir)))
1070 {
1071 directories.append(temp_dir);
1072 }
1073
1074 return directories;
1075}
1076
1081
1082bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_maxncpy)
1083{
1085
1086 for (const std::string &directory : directories) {
1087 BLI_path_join(path, path_maxncpy, directory.c_str(), app_template);
1088 if (BLI_is_dir(path)) {
1089 return true;
1090 }
1091 }
1092
1093 return false;
1094}
1095
1097{
1098 /* Test if app template provides a `userpref.blend`.
1099 * If not, we will share user preferences with the rest of Blender. */
1100 if (app_template[0] == '\0') {
1101 return false;
1102 }
1103
1104 char app_template_path[FILE_MAX];
1106 app_template, app_template_path, sizeof(app_template_path)))
1107 {
1108 return false;
1109 }
1110
1111 char userpref_path[FILE_MAX];
1112 BLI_path_join(userpref_path, sizeof(userpref_path), app_template_path, BLENDER_USERPREF_FILE);
1113 return BLI_exists(userpref_path);
1114}
1115
1117{
1118 BLI_listbase_clear(templates);
1119
1121
1122 for (const std::string &subdir : directories) {
1123 direntry *dirs;
1124 const uint dir_num = BLI_filelist_dir_contents(subdir.c_str(), &dirs);
1125 for (int f = 0; f < dir_num; f++) {
1126 if (!FILENAME_IS_CURRPAR(dirs[f].relname) && S_ISDIR(dirs[f].type)) {
1127 char *app_template = BLI_strdup(dirs[f].relname);
1129 }
1130 }
1131
1132 BLI_filelist_free(dirs, dir_num);
1133 }
1134}
1135
1137
1138/* -------------------------------------------------------------------- */
1141
1154static bool where_is_temp(char *tempdir, const size_t tempdir_maxncpy, const char *userdir)
1155{
1156 if (userdir) {
1157 return BLI_temp_directory_path_copy_if_valid(tempdir, tempdir_maxncpy, userdir);
1158 }
1159 BLI_temp_directory_path_get(tempdir, tempdir_maxncpy);
1160 return true;
1161}
1162
1163static bool tempdir_session_create(char *tempdir_session,
1164 const size_t tempdir_session_maxncpy,
1165 const char *tempdir)
1166{
1167 tempdir_session[0] = '\0';
1168
1169 const int tempdir_len = strlen(tempdir);
1170 /* 'XXXXXX' is kind of tag to be replaced by `mktemp-family` by an UUID. */
1171 const char *session_name = "blender_XXXXXX";
1172 const int session_name_len = strlen(session_name);
1173
1174 /* +1 as a slash is added,
1175 * #_mktemp_s also requires the last null character is included. */
1176 const int tempdir_session_len_required = tempdir_len + session_name_len + 1;
1177
1178 if (tempdir_session_len_required <= tempdir_session_maxncpy) {
1179 /* No need to use path joining utility as we know the last character of #tempdir is a slash. */
1180 BLI_string_join(tempdir_session, tempdir_session_maxncpy, tempdir, session_name);
1181#ifdef WIN32
1182 const bool needs_create = (_mktemp_s(tempdir_session, tempdir_session_len_required) == 0);
1183#else
1184 const bool needs_create = (mkdtemp(tempdir_session) == nullptr);
1185#endif
1186 if (needs_create) {
1187 BLI_dir_create_recursive(tempdir_session);
1188 }
1189 if (BLI_is_dir(tempdir_session)) {
1190 BLI_path_slash_ensure(tempdir_session, tempdir_session_maxncpy);
1191 /* Success. */
1192 return true;
1193 }
1194 }
1195
1196 CLOG_WARN(&LOG, "Could not generate a temp file name for '%s'", tempdir_session);
1197 return false;
1198}
1199
1200void BKE_tempdir_init(const char *userdir)
1201{
1202 /* Sets #g_app.temp_dirname_base to `userdir` if specified and is a valid directory,
1203 * otherwise chooses a suitable OS-specific temporary directory.
1204 * Sets #g_app.temp_dirname_session to a #mkdtemp
1205 * generated sub-dir of #g_app.temp_dirname_base. */
1206
1207 /* Clear existing temp dir, if needed. */
1209
1210 g_app.temp_dirname_session_can_be_deleted = false;
1211
1212 /* Only do one pass if `userdir` is null. */
1213 int userdir_args_num = userdir ? 2 : 1;
1214 const char *userdir_args[2] = {userdir, nullptr};
1215
1216 for (int i = 0; i < userdir_args_num; i++) {
1217 if (where_is_temp(g_app.temp_dirname_base, sizeof(g_app.temp_dirname_base), userdir_args[i])) {
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 g_app.temp_dirname_session_can_be_deleted = true;
1223 break;
1224 }
1225 }
1226 }
1227
1228 if (UNLIKELY(g_app.temp_dirname_session_can_be_deleted == false)) {
1229 /* This should practically never happen as either the preferences or the systems
1230 * default temporary directory should be usable, if not, use the base directory and warn. */
1231 STRNCPY(g_app.temp_dirname_session, g_app.temp_dirname_base);
1232 CLOG_WARN(&LOG,
1233 "Could not generate a temp session subdirectory, falling back to '%s'",
1234 g_app.temp_dirname_base);
1235 }
1236}
1237
1239{
1240 return g_app.temp_dirname_session[0] ? g_app.temp_dirname_session : BKE_tempdir_base();
1241}
1242
1243const char *BKE_tempdir_base()
1244{
1245 return g_app.temp_dirname_base;
1246}
1247
1249{
1250 if (g_app.temp_dirname_session_can_be_deleted == false) {
1251 /* It's possible this path references an arbitrary location
1252 * in that case *never* recursively remove, see: #139585. */
1253 return;
1254 }
1255 if (g_app.temp_dirname_session[0] && BLI_is_dir(g_app.temp_dirname_session)) {
1256 BLI_delete(g_app.temp_dirname_session, true, true);
1257 }
1258}
1259
@ BLENDER_RESOURCE_PATH_SYSTEM
@ BLENDER_RESOURCE_PATH_LOCAL
@ BLENDER_RESOURCE_PATH_USER
#define BLENDER_USERPREF_FILE
@ 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_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:360
bool BLI_dir_create_recursive(const char *dirname) ATTR_NONNULL()
Definition fileops_c.cc:414
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:443
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:97
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:931
#define SNPRINTF(dst, format,...)
Definition BLI_string.h:604
char * STRNCPY(char(&dst)[N], const char *src)
Definition BLI_string.h:693
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:188
#define CLOG_DEBUG(clg_ref,...)
Definition CLG_log.h:191
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
std::optional< std::string > 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:1200
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:781
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:495
static bool get_path_environment(char *targetpath, size_t targetpath_maxncpy, const char *subfolder_name, const char *envvar)
Definition appdir.cc:438
std::optional< std::string > BKE_appdir_resource_path_id(const int folder_id, const bool check_is_dir)
Definition appdir.cc:834
const char * BKE_appdir_folder_root()
Definition appdir.cc:152
const char * BKE_tempdir_session()
Definition appdir.cc:1238
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:730
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:319
bool BKE_appdir_app_template_has_userpref(const char *app_template)
Definition appdir.cc:1096
const char * BKE_appdir_program_dir()
Definition appdir.cc:964
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:279
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:970
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:556
void BKE_appdir_program_path_init(const char *argv0)
Definition appdir.cc:933
static bool where_is_temp(char *tempdir, const size_t tempdir_maxncpy, const char *userdir)
Definition appdir.cc:1154
static struct @117045314216322207174324056210201300111320007173 g_app
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:806
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:608
static bool tempdir_session_create(char *tempdir_session, const size_t tempdir_session_maxncpy, const char *tempdir)
Definition appdir.cc:1163
static char * blender_version_decimal(const int version)
Definition appdir.cc:123
static blender::Vector< std::string > appdir_app_template_directories()
Definition appdir.cc:1047
#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:403
static blender::Vector< std::string > get_path_environment_multiple(const char *subfolder_name, const char *envvar, const bool check_is_dir)
Definition appdir.cc:448
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:721
bool BKE_appdir_app_template_any()
Definition appdir.cc:1077
void BKE_appdir_app_templates(ListBase *templates)
Definition appdir.cc:1116
bool BKE_appdir_font_folder_default(char *dir, size_t dir_maxncpy)
Definition appdir.cc:233
bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_maxncpy)
Definition appdir.cc:1082
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:360
const char * BKE_appdir_program_path()
Definition appdir.cc:956
char temp_dirname_base[FILE_MAX]
Definition appdir.cc:70
void BKE_tempdir_session_purge()
Definition appdir.cc:1248
static void where_am_i(char *program_filepath, const size_t program_filepath_maxncpy, const char *program_name)
Definition appdir.cc:861
static bool get_path_system(char *targetpath, size_t targetpath_maxncpy, const char *folder_name, const char *subfolder_name)
Definition appdir.cc:591
static bool get_path_user(char *targetpath, size_t targetpath_maxncpy, const char *folder_name, const char *subfolder_name)
Definition appdir.cc:535
char program_filepath[FILE_MAX]
Definition appdir.cc:66
const char * BKE_tempdir_base()
Definition appdir.cc:1243
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:423
bool BKE_appdir_folder_caches(char *path, const size_t path_maxncpy)
Definition appdir.cc:203
bool is_empty() const
void append(const T &value)
void extend(Span< T > array)
#define LOG(level)
Definition log.h:97
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:1191