The goal here is to make app templates usable for default templates that we can ship with Blender. These only have a custom startup.blend currently and so are quite limited compared to app templates that fully customize Blender. But still it seems like the same kind of concept where we should be sharing the code and UI. It is useful to be able to save a startup.blend per template, and I can imagine some scripting being useful in the future as well. Changes made: * File > New and Ctrl+N now list the templates, replacing a separate Application Templates menu that was not as easy to discover. * File menu now shows name of active template above Save Startup File and Load Factory Settings to indicate these are saved/loaded per template. * The "Default" template was renamed to "General". * Workspaces can now be added from any of the template startup.blend files when clicking the (+) button in the topbar. * User preferences are now fully shared between app templates, unless the template includes a custom userpref.blend. I think this will be useful in general, not all app templates need their own keymaps for example. * Previously Save User Preferences would save the current app template and then Blender would start using that template by default. I've disabled this, to me it seems it was unintentional, or at least not clear at all that saving user preferences also makes the current Differential Revision: https://developer.blender.org/D3690
907 lines
25 KiB
C
907 lines
25 KiB
C
/*
|
|
* ***** BEGIN GPL LICENSE BLOCK *****
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
/** \file blender/blenkernel/intern/appdir.c
|
|
* \ingroup bke
|
|
*
|
|
* Access to application level directories.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
|
|
#include "BLI_utildefines.h"
|
|
#include "BLI_fileops.h"
|
|
#include "BLI_fileops_types.h"
|
|
#include "BLI_listbase.h"
|
|
#include "BLI_path_util.h"
|
|
#include "BLI_string.h"
|
|
|
|
#include "BKE_blender_version.h"
|
|
#include "BKE_appdir.h" /* own include */
|
|
|
|
#include "GHOST_Path-api.h"
|
|
|
|
#include "MEM_guardedalloc.h"
|
|
|
|
#ifdef WIN32
|
|
# include "utf_winfunc.h"
|
|
# include "utfconv.h"
|
|
# include <io.h>
|
|
# ifdef _WIN32_IE
|
|
# undef _WIN32_IE
|
|
# endif
|
|
# define _WIN32_IE 0x0501
|
|
# include <windows.h>
|
|
# include <shlobj.h>
|
|
# include "BLI_winstuff.h"
|
|
#else /* non windows */
|
|
# ifdef WITH_BINRELOC
|
|
# include "binreloc.h"
|
|
# endif
|
|
# include <unistd.h> /* mkdtemp on OSX (and probably all *BSD?), not worth making specific check for this OS. */
|
|
#endif /* WIN32 */
|
|
|
|
/* local */
|
|
static char bprogname[FILE_MAX]; /* full path to program executable */
|
|
static char bprogdir[FILE_MAX]; /* full path to directory in which executable is located */
|
|
static char btempdir_base[FILE_MAX]; /* persistent temporary directory */
|
|
static char btempdir_session[FILE_MAX] = ""; /* volatile temporary directory */
|
|
|
|
/* This is now only used to really get the user's default document folder */
|
|
/* On Windows I chose the 'Users/<MyUserName>/Documents' since it's used
|
|
* as default location to save documents */
|
|
const char *BKE_appdir_folder_default(void)
|
|
{
|
|
#ifndef WIN32
|
|
const char * const xdg_documents_dir = BLI_getenv("XDG_DOCUMENTS_DIR");
|
|
|
|
if (xdg_documents_dir)
|
|
return xdg_documents_dir;
|
|
|
|
return BLI_getenv("HOME");
|
|
#else /* Windows */
|
|
static char documentfolder[MAXPATHLEN];
|
|
HRESULT hResult;
|
|
|
|
/* Check for %HOME% env var */
|
|
if (uput_getenv("HOME", documentfolder, MAXPATHLEN)) {
|
|
if (BLI_is_dir(documentfolder)) return documentfolder;
|
|
}
|
|
|
|
/* add user profile support for WIN 2K / NT.
|
|
* This is %APPDATA%, which translates to either
|
|
* %USERPROFILE%\Application Data or since Vista
|
|
* to %USERPROFILE%\AppData\Roaming
|
|
*/
|
|
hResult = SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, documentfolder);
|
|
|
|
if (hResult == S_OK) {
|
|
if (BLI_is_dir(documentfolder)) return documentfolder;
|
|
}
|
|
|
|
return NULL;
|
|
#endif /* WIN32 */
|
|
}
|
|
|
|
|
|
// #define PATH_DEBUG
|
|
|
|
/* returns a formatted representation of the specified version number. Non-reentrant! */
|
|
static char *blender_version_decimal(const int ver)
|
|
{
|
|
static char version_str[5];
|
|
BLI_assert(ver < 1000);
|
|
BLI_snprintf(version_str, sizeof(version_str), "%d.%02d", ver / 100, ver % 100);
|
|
return version_str;
|
|
}
|
|
|
|
/**
|
|
* Concatenates path_base, (optional) path_sep and (optional) folder_name into targetpath,
|
|
* returning true if result points to a directory.
|
|
*/
|
|
static bool test_path(
|
|
char *targetpath, size_t targetpath_len,
|
|
const char *path_base, const char *path_sep, const char *folder_name)
|
|
{
|
|
char tmppath[FILE_MAX];
|
|
|
|
if (path_sep) {
|
|
BLI_join_dirfile(tmppath, sizeof(tmppath), path_base, path_sep);
|
|
}
|
|
else {
|
|
BLI_strncpy(tmppath, path_base, sizeof(tmppath));
|
|
}
|
|
|
|
/* rare cases folder_name is omitted (when looking for ~/.config/blender/2.xx dir only) */
|
|
if (folder_name) {
|
|
BLI_join_dirfile(targetpath, targetpath_len, tmppath, folder_name);
|
|
}
|
|
else {
|
|
BLI_strncpy(targetpath, tmppath, targetpath_len);
|
|
}
|
|
/* FIXME: why is "//" on front of tmppath expanded to "/" (by BLI_join_dirfile)
|
|
* if folder_name is specified but not otherwise? */
|
|
|
|
if (BLI_is_dir(targetpath)) {
|
|
#ifdef PATH_DEBUG
|
|
printf("\t%s found: %s\n", __func__, targetpath);
|
|
#endif
|
|
return true;
|
|
}
|
|
else {
|
|
#ifdef PATH_DEBUG
|
|
printf("\t%s missing: %s\n", __func__, targetpath);
|
|
#endif
|
|
//targetpath[0] = '\0';
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Puts the value of the specified environment variable into *path if it exists
|
|
* and points at a directory. Returns true if this was done.
|
|
*/
|
|
static bool test_env_path(char *path, const char *envvar)
|
|
{
|
|
const char *env = envvar ? BLI_getenv(envvar) : NULL;
|
|
if (!env) return false;
|
|
|
|
if (BLI_is_dir(env)) {
|
|
BLI_strncpy(path, env, FILE_MAX);
|
|
#ifdef PATH_DEBUG
|
|
printf("\t%s env %s found: %s\n", __func__, envvar, env);
|
|
#endif
|
|
return true;
|
|
}
|
|
else {
|
|
path[0] = '\0';
|
|
#ifdef PATH_DEBUG
|
|
printf("\t%s env %s missing: %s\n", __func__, envvar, env);
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructs in \a targetpath the name of a directory relative to a version-specific
|
|
* subdirectory in the parent directory of the Blender executable.
|
|
*
|
|
* \param targetpath String to return path
|
|
* \param folder_name Optional folder name within version-specific directory
|
|
* \param subfolder_name Optional subfolder name within folder_name
|
|
* \param ver To construct name of version-specific directory within bprogdir
|
|
* \return true if such a directory exists.
|
|
*/
|
|
static bool get_path_local(
|
|
char *targetpath, size_t targetpath_len,
|
|
const char *folder_name, const char *subfolder_name, const int ver)
|
|
{
|
|
char relfolder[FILE_MAX];
|
|
|
|
#ifdef PATH_DEBUG
|
|
printf("%s...\n", __func__);
|
|
#endif
|
|
|
|
if (folder_name) {
|
|
if (subfolder_name) {
|
|
BLI_join_dirfile(relfolder, sizeof(relfolder), folder_name, subfolder_name);
|
|
}
|
|
else {
|
|
BLI_strncpy(relfolder, folder_name, sizeof(relfolder));
|
|
}
|
|
}
|
|
else {
|
|
relfolder[0] = '\0';
|
|
}
|
|
|
|
/* try EXECUTABLE_DIR/2.5x/folder_name - new default directory for local blender installed files */
|
|
#ifdef __APPLE__
|
|
/* due new codesign situation in OSX > 10.9.5 we must move the blender_version dir with contents to Resources */
|
|
char osx_resourses[FILE_MAX];
|
|
BLI_snprintf(osx_resourses, sizeof(osx_resourses), "%s../Resources", bprogdir);
|
|
/* Remove the '/../' added above. */
|
|
BLI_cleanup_path(NULL, osx_resourses);
|
|
return test_path(targetpath, targetpath_len, osx_resourses, blender_version_decimal(ver), relfolder);
|
|
#else
|
|
return test_path(targetpath, targetpath_len, bprogdir, blender_version_decimal(ver), relfolder);
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Is this an install with user files kept together with the Blender executable and its
|
|
* installation files.
|
|
*/
|
|
static bool is_portable_install(void)
|
|
{
|
|
/* detect portable install by the existence of config folder */
|
|
const int ver = BLENDER_VERSION;
|
|
char path[FILE_MAX];
|
|
|
|
return get_path_local(path, sizeof(path), "config", NULL, ver);
|
|
}
|
|
|
|
/**
|
|
* Returns the path of a folder from environment variables
|
|
*
|
|
* \param targetpath: String to return path.
|
|
* \param subfolder_name: optional name of subfolder within folder.
|
|
* \param envvar: name of environment variable to check folder_name.
|
|
* \return true if it was able to construct such a path.
|
|
*/
|
|
static bool get_path_environment(
|
|
char *targetpath,
|
|
size_t targetpath_len,
|
|
const char *subfolder_name,
|
|
const char *envvar)
|
|
{
|
|
char user_path[FILE_MAX];
|
|
|
|
if (test_env_path(user_path, envvar)) {
|
|
if (subfolder_name) {
|
|
return test_path(
|
|
targetpath,
|
|
targetpath_len,
|
|
user_path,
|
|
NULL,
|
|
subfolder_name);
|
|
}
|
|
else {
|
|
BLI_strncpy(targetpath, user_path, FILE_MAX);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Returns the path of a folder within the user-files area.
|
|
*
|
|
*
|
|
* \param targetpath String to return path
|
|
* \param folder_name default name of folder within user area
|
|
* \param subfolder_name optional name of subfolder within folder
|
|
* \param ver Blender version, used to construct a subdirectory name
|
|
* \return true if it was able to construct such a path.
|
|
*/
|
|
static bool get_path_user(
|
|
char *targetpath, size_t targetpath_len, const char *folder_name, const char *subfolder_name,
|
|
const int ver)
|
|
{
|
|
char user_path[FILE_MAX];
|
|
const char *user_base_path;
|
|
|
|
/* for portable install, user path is always local */
|
|
if (is_portable_install()) {
|
|
return get_path_local(targetpath, targetpath_len, folder_name, subfolder_name, ver);
|
|
}
|
|
user_path[0] = '\0';
|
|
|
|
user_base_path = (const char *)GHOST_getUserDir(ver, blender_version_decimal(ver));
|
|
if (user_base_path)
|
|
BLI_strncpy(user_path, user_base_path, FILE_MAX);
|
|
|
|
if (!user_path[0])
|
|
return false;
|
|
|
|
#ifdef PATH_DEBUG
|
|
printf("%s: %s\n", __func__, user_path);
|
|
#endif
|
|
|
|
if (subfolder_name) {
|
|
return test_path(targetpath, targetpath_len, user_path, folder_name, subfolder_name);
|
|
}
|
|
else {
|
|
return test_path(targetpath, targetpath_len, user_path, NULL, folder_name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the path of a folder within the Blender installation directory.
|
|
*
|
|
* \param targetpath String to return path
|
|
* \param folder_name default name of folder within installation area
|
|
* \param subfolder_name optional name of subfolder within folder
|
|
* \param ver Blender version, used to construct a subdirectory name
|
|
* \return true if it was able to construct such a path.
|
|
*/
|
|
static bool get_path_system(
|
|
char *targetpath, size_t targetpath_len, const char *folder_name, const char *subfolder_name,
|
|
const int ver)
|
|
{
|
|
char system_path[FILE_MAX];
|
|
const char *system_base_path;
|
|
char relfolder[FILE_MAX];
|
|
|
|
if (folder_name) {
|
|
if (subfolder_name) {
|
|
BLI_join_dirfile(relfolder, sizeof(relfolder), folder_name, subfolder_name);
|
|
}
|
|
else {
|
|
BLI_strncpy(relfolder, folder_name, sizeof(relfolder));
|
|
}
|
|
}
|
|
else {
|
|
relfolder[0] = '\0';
|
|
}
|
|
|
|
system_path[0] = '\0';
|
|
system_base_path = (const char *)GHOST_getSystemDir(ver, blender_version_decimal(ver));
|
|
if (system_base_path)
|
|
BLI_strncpy(system_path, system_base_path, FILE_MAX);
|
|
|
|
if (!system_path[0])
|
|
return false;
|
|
|
|
#ifdef PATH_DEBUG
|
|
printf("%s: %s\n", __func__, system_path);
|
|
#endif
|
|
|
|
if (subfolder_name) {
|
|
/* try $BLENDERPATH/folder_name/subfolder_name */
|
|
return test_path(targetpath, targetpath_len, system_path, folder_name, subfolder_name);
|
|
}
|
|
else {
|
|
/* try $BLENDERPATH/folder_name */
|
|
return test_path(targetpath, targetpath_len, system_path, NULL, folder_name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get a folder out of the 'folder_id' presets for paths.
|
|
* returns the path if found, NULL string if not
|
|
*
|
|
* \param subfolder: The name of a directory to check for,
|
|
* this may contain path separators but must resolve to a directory, checked with #BLI_is_dir.
|
|
*/
|
|
const char *BKE_appdir_folder_id_ex(
|
|
const int folder_id, const char *subfolder,
|
|
char *path, size_t path_len)
|
|
{
|
|
const int ver = BLENDER_VERSION;
|
|
|
|
switch (folder_id) {
|
|
case BLENDER_DATAFILES: /* general case */
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) break;
|
|
if (get_path_user(path, path_len, "datafiles", subfolder, ver)) break;
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_DATAFILES")) break;
|
|
if (get_path_local(path, path_len, "datafiles", subfolder, ver)) break;
|
|
if (get_path_system(path, path_len, "datafiles", subfolder, ver)) break;
|
|
return NULL;
|
|
|
|
case BLENDER_USER_DATAFILES:
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) break;
|
|
if (get_path_user(path, path_len, "datafiles", subfolder, ver)) break;
|
|
return NULL;
|
|
|
|
case BLENDER_SYSTEM_DATAFILES:
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_DATAFILES")) break;
|
|
if (get_path_system(path, path_len, "datafiles", subfolder, ver)) break;
|
|
if (get_path_local(path, path_len, "datafiles", subfolder, ver)) break;
|
|
return NULL;
|
|
|
|
case BLENDER_USER_AUTOSAVE:
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_DATAFILES")) break;
|
|
if (get_path_user(path, path_len, "autosave", subfolder, ver)) break;
|
|
return NULL;
|
|
|
|
case BLENDER_USER_CONFIG:
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_CONFIG")) break;
|
|
if (get_path_user(path, path_len, "config", subfolder, ver)) break;
|
|
return NULL;
|
|
|
|
case BLENDER_USER_SCRIPTS:
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_USER_SCRIPTS")) break;
|
|
if (get_path_user(path, path_len, "scripts", subfolder, ver)) break;
|
|
return NULL;
|
|
|
|
case BLENDER_SYSTEM_SCRIPTS:
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_SCRIPTS")) break;
|
|
if (get_path_system(path, path_len, "scripts", subfolder, ver)) break;
|
|
if (get_path_local(path, path_len, "scripts", subfolder, ver)) break;
|
|
return NULL;
|
|
|
|
case BLENDER_SYSTEM_PYTHON:
|
|
if (get_path_environment(path, path_len, subfolder, "BLENDER_SYSTEM_PYTHON")) break;
|
|
if (get_path_system(path, path_len, "python", subfolder, ver)) break;
|
|
if (get_path_local(path, path_len, "python", subfolder, ver)) break;
|
|
return NULL;
|
|
|
|
default:
|
|
BLI_assert(0);
|
|
break;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
const char *BKE_appdir_folder_id(
|
|
const int folder_id, const char *subfolder)
|
|
{
|
|
static char path[FILE_MAX] = "";
|
|
return BKE_appdir_folder_id_ex(folder_id, subfolder, path, sizeof(path));
|
|
}
|
|
|
|
/**
|
|
* Returns the path to a folder in the user area without checking that it actually exists first.
|
|
*/
|
|
const char *BKE_appdir_folder_id_user_notest(const int folder_id, const char *subfolder)
|
|
{
|
|
const int ver = BLENDER_VERSION;
|
|
static char path[FILE_MAX] = "";
|
|
|
|
switch (folder_id) {
|
|
case BLENDER_USER_DATAFILES:
|
|
if (get_path_environment(path, sizeof(path), subfolder, "BLENDER_USER_DATAFILES")) break;
|
|
get_path_user(path, sizeof(path), "datafiles", subfolder, ver);
|
|
break;
|
|
case BLENDER_USER_CONFIG:
|
|
if (get_path_environment(path, sizeof(path), subfolder, "BLENDER_USER_CONFIG")) break;
|
|
get_path_user(path, sizeof(path), "config", subfolder, ver);
|
|
break;
|
|
case BLENDER_USER_AUTOSAVE:
|
|
if (get_path_environment(path, sizeof(path), subfolder, "BLENDER_USER_AUTOSAVE")) break;
|
|
get_path_user(path, sizeof(path), "autosave", subfolder, ver);
|
|
break;
|
|
case BLENDER_USER_SCRIPTS:
|
|
if (get_path_environment(path, sizeof(path), subfolder, "BLENDER_USER_SCRIPTS")) break;
|
|
get_path_user(path, sizeof(path), "scripts", subfolder, ver);
|
|
break;
|
|
default:
|
|
BLI_assert(0);
|
|
break;
|
|
}
|
|
|
|
if ('\0' == path[0]) {
|
|
return NULL;
|
|
}
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* Returns the path to a folder in the user area, creating it if it doesn't exist.
|
|
*/
|
|
const char *BKE_appdir_folder_id_create(const int folder_id, const char *subfolder)
|
|
{
|
|
const char *path;
|
|
|
|
/* only for user folders */
|
|
if (!ELEM(folder_id, BLENDER_USER_DATAFILES, BLENDER_USER_CONFIG, BLENDER_USER_SCRIPTS, BLENDER_USER_AUTOSAVE))
|
|
return NULL;
|
|
|
|
path = BKE_appdir_folder_id(folder_id, subfolder);
|
|
|
|
if (!path) {
|
|
path = BKE_appdir_folder_id_user_notest(folder_id, subfolder);
|
|
if (path) BLI_dir_create_recursive(path);
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
/**
|
|
* Returns the path of the top-level version-specific local, user or system directory.
|
|
* If do_check, then the result will be NULL if the directory doesn't exist.
|
|
*/
|
|
const char *BKE_appdir_folder_id_version(const int folder_id, const int ver, const bool do_check)
|
|
{
|
|
static char path[FILE_MAX] = "";
|
|
bool ok;
|
|
switch (folder_id) {
|
|
case BLENDER_RESOURCE_PATH_USER:
|
|
ok = get_path_user(path, sizeof(path), NULL, NULL, ver);
|
|
break;
|
|
case BLENDER_RESOURCE_PATH_LOCAL:
|
|
ok = get_path_local(path, sizeof(path), NULL, NULL, ver);
|
|
break;
|
|
case BLENDER_RESOURCE_PATH_SYSTEM:
|
|
ok = get_path_system(path, sizeof(path), NULL, NULL, ver);
|
|
break;
|
|
default:
|
|
path[0] = '\0'; /* in case do_check is false */
|
|
ok = false;
|
|
BLI_assert(!"incorrect ID");
|
|
break;
|
|
}
|
|
|
|
if (!ok && do_check) {
|
|
return NULL;
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
#ifdef PATH_DEBUG
|
|
# undef PATH_DEBUG
|
|
#endif
|
|
|
|
|
|
/* -------------------------------------------------------------------- */
|
|
/* Preset paths */
|
|
|
|
/**
|
|
* Checks if name is a fully qualified filename to an executable.
|
|
* If not it searches $PATH for the file. On Windows it also
|
|
* adds the correct extension (.com .exe etc) from
|
|
* $PATHEXT if necessary. Also on Windows it translates
|
|
* the name to its 8.3 version to prevent problems with
|
|
* spaces and stuff. Final result is returned in fullname.
|
|
*
|
|
* \param fullname The full path and full name of the executable
|
|
* (must be FILE_MAX minimum)
|
|
* \param name The name of the executable (usually argv[0]) to be checked
|
|
*/
|
|
static void where_am_i(char *fullname, const size_t maxlen, const char *name)
|
|
{
|
|
#ifdef WITH_BINRELOC
|
|
/* linux uses binreloc since argv[0] is not reliable, call br_init( NULL ) first */
|
|
{
|
|
const char *path = NULL;
|
|
path = br_find_exe(NULL);
|
|
if (path) {
|
|
BLI_strncpy(fullname, path, maxlen);
|
|
free((void *)path);
|
|
return;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
{
|
|
wchar_t *fullname_16 = MEM_mallocN(maxlen * sizeof(wchar_t), "ProgramPath");
|
|
if (GetModuleFileNameW(0, fullname_16, maxlen)) {
|
|
conv_utf_16_to_8(fullname_16, fullname, maxlen);
|
|
if (!BLI_exists(fullname)) {
|
|
printf("path can't be found: \"%.*s\"\n", (int)maxlen, fullname);
|
|
MessageBox(NULL, "path contains invalid characters or is too long (see console)", "Error", MB_OK);
|
|
}
|
|
MEM_freeN(fullname_16);
|
|
return;
|
|
}
|
|
|
|
MEM_freeN(fullname_16);
|
|
}
|
|
#endif
|
|
|
|
/* unix and non linux */
|
|
if (name && name[0]) {
|
|
|
|
BLI_strncpy(fullname, name, maxlen);
|
|
if (name[0] == '.') {
|
|
BLI_path_cwd(fullname, maxlen);
|
|
#ifdef _WIN32
|
|
BLI_path_program_extensions_add_win32(fullname, maxlen);
|
|
#endif
|
|
}
|
|
else if (BLI_last_slash(name)) {
|
|
// full path
|
|
BLI_strncpy(fullname, name, maxlen);
|
|
#ifdef _WIN32
|
|
BLI_path_program_extensions_add_win32(fullname, maxlen);
|
|
#endif
|
|
}
|
|
else {
|
|
BLI_path_program_search(fullname, maxlen, name);
|
|
}
|
|
/* Remove "/./" and "/../" so string comparisons can be used on the path. */
|
|
BLI_cleanup_path(NULL, fullname);
|
|
|
|
#if defined(DEBUG)
|
|
if (!STREQ(name, fullname)) {
|
|
printf("guessing '%s' == '%s'\n", name, fullname);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
void BKE_appdir_program_path_init(const char *argv0)
|
|
{
|
|
where_am_i(bprogname, sizeof(bprogname), argv0);
|
|
BLI_split_dir_part(bprogname, bprogdir, sizeof(bprogdir));
|
|
}
|
|
|
|
/**
|
|
* Path to executable
|
|
*/
|
|
const char *BKE_appdir_program_path(void)
|
|
{
|
|
return bprogname;
|
|
}
|
|
|
|
/**
|
|
* Path to directory of executable
|
|
*/
|
|
const char *BKE_appdir_program_dir(void)
|
|
{
|
|
return bprogdir;
|
|
}
|
|
|
|
bool BKE_appdir_program_python_search(
|
|
char *fullpath, const size_t fullpath_len,
|
|
const int version_major, const int version_minor)
|
|
{
|
|
#ifdef PYTHON_EXECUTABLE_NAME
|
|
/* passed in from the build-systems 'PYTHON_EXECUTABLE' */
|
|
const char *python_build_def = STRINGIFY(PYTHON_EXECUTABLE_NAME);
|
|
#endif
|
|
const char *basename = "python";
|
|
char python_ver[16];
|
|
/* check both possible names */
|
|
const char *python_names[] = {
|
|
#ifdef PYTHON_EXECUTABLE_NAME
|
|
python_build_def,
|
|
#endif
|
|
python_ver,
|
|
basename,
|
|
};
|
|
int i;
|
|
|
|
bool is_found = false;
|
|
|
|
BLI_snprintf(python_ver, sizeof(python_ver), "%s%d.%d", basename, version_major, version_minor);
|
|
|
|
{
|
|
const char *python_bin_dir = BKE_appdir_folder_id(BLENDER_SYSTEM_PYTHON, "bin");
|
|
if (python_bin_dir) {
|
|
|
|
for (i = 0; i < ARRAY_SIZE(python_names); i++) {
|
|
BLI_join_dirfile(fullpath, fullpath_len, python_bin_dir, python_names[i]);
|
|
|
|
if (
|
|
#ifdef _WIN32
|
|
BLI_path_program_extensions_add_win32(fullpath, fullpath_len)
|
|
#else
|
|
BLI_exists(fullpath)
|
|
#endif
|
|
)
|
|
{
|
|
is_found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_found == false) {
|
|
for (i = 0; i < ARRAY_SIZE(python_names); i++) {
|
|
if (BLI_path_program_search(fullpath, fullpath_len, python_names[i])) {
|
|
is_found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_found == false) {
|
|
*fullpath = '\0';
|
|
}
|
|
|
|
return is_found;
|
|
}
|
|
|
|
/** Keep in sync with `bpy.utils.app_template_paths()` */
|
|
static const char *app_template_directory_search[2] = {
|
|
"startup" SEP_STR "bl_app_templates_user",
|
|
"startup" SEP_STR "bl_app_templates_system",
|
|
};
|
|
|
|
static const int app_template_directory_id[2] = {
|
|
/* Only 'USER' */
|
|
BLENDER_USER_SCRIPTS,
|
|
/* Covers 'LOCAL' & 'SYSTEM'. */
|
|
BLENDER_SYSTEM_SCRIPTS,
|
|
};
|
|
|
|
/**
|
|
* Return true if templates exist
|
|
*/
|
|
bool BKE_appdir_app_template_any(void)
|
|
{
|
|
char temp_dir[FILE_MAX];
|
|
for (int i = 0; i < 2; i++) {
|
|
if (BKE_appdir_folder_id_ex(
|
|
app_template_directory_id[i], app_template_directory_search[i],
|
|
temp_dir, sizeof(temp_dir)))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool BKE_appdir_app_template_id_search(const char *app_template, char *path, size_t path_len)
|
|
{
|
|
for (int i = 0; i < 2; i++) {
|
|
char subdir[FILE_MAX];
|
|
BLI_join_dirfile(subdir, sizeof(subdir), app_template_directory_search[i], app_template);
|
|
if (BKE_appdir_folder_id_ex(
|
|
app_template_directory_id[i], subdir,
|
|
path, path_len))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void BKE_appdir_app_templates(ListBase *templates)
|
|
{
|
|
BLI_listbase_clear(templates);
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
char subdir[FILE_MAX];
|
|
if (!BKE_appdir_folder_id_ex(
|
|
app_template_directory_id[i], app_template_directory_search[i],
|
|
subdir, sizeof(subdir)))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
struct direntry *dir;
|
|
uint totfile = BLI_filelist_dir_contents(subdir, &dir);
|
|
for (int f = 0; f < totfile; f++) {
|
|
if (!FILENAME_IS_CURRPAR(dir[f].relname) && S_ISDIR(dir[f].type)) {
|
|
char *template = BLI_strdup(dir[f].relname);
|
|
BLI_addtail(templates, BLI_genericNodeN(template));
|
|
}
|
|
}
|
|
|
|
BLI_filelist_free(dir, totfile);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the temp directory when blender first runs.
|
|
* If the default path is not found, use try $TEMP
|
|
*
|
|
* Also make sure the temp dir has a trailing slash
|
|
*
|
|
* \param fullname The full path to the temporary temp directory
|
|
* \param basename The full path to the persistent temp directory (may be NULL)
|
|
* \param maxlen The size of the fullname buffer
|
|
* \param userdir Directory specified in user preferences
|
|
*/
|
|
static void where_is_temp(char *fullname, char *basename, const size_t maxlen, char *userdir)
|
|
{
|
|
/* Clear existing temp dir, if needed. */
|
|
BKE_tempdir_session_purge();
|
|
|
|
fullname[0] = '\0';
|
|
if (basename) {
|
|
basename[0] = '\0';
|
|
}
|
|
|
|
if (userdir && BLI_is_dir(userdir)) {
|
|
BLI_strncpy(fullname, userdir, maxlen);
|
|
}
|
|
|
|
|
|
#ifdef WIN32
|
|
if (fullname[0] == '\0') {
|
|
const char *tmp = BLI_getenv("TEMP"); /* Windows */
|
|
if (tmp && BLI_is_dir(tmp)) {
|
|
BLI_strncpy(fullname, tmp, maxlen);
|
|
}
|
|
}
|
|
#else
|
|
/* Other OS's - Try TMP and TMPDIR */
|
|
if (fullname[0] == '\0') {
|
|
const char *tmp = BLI_getenv("TMP");
|
|
if (tmp && BLI_is_dir(tmp)) {
|
|
BLI_strncpy(fullname, tmp, maxlen);
|
|
}
|
|
}
|
|
|
|
if (fullname[0] == '\0') {
|
|
const char *tmp = BLI_getenv("TMPDIR");
|
|
if (tmp && BLI_is_dir(tmp)) {
|
|
BLI_strncpy(fullname, tmp, maxlen);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (fullname[0] == '\0') {
|
|
BLI_strncpy(fullname, "/tmp/", maxlen);
|
|
}
|
|
else {
|
|
/* add a trailing slash if needed */
|
|
BLI_add_slash(fullname);
|
|
#ifdef WIN32
|
|
if (userdir && userdir != fullname) {
|
|
/* also set user pref to show %TEMP%. /tmp/ is just plain confusing for Windows users. */
|
|
BLI_strncpy(userdir, fullname, maxlen);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Now that we have a valid temp dir, add system-generated unique sub-dir. */
|
|
if (basename) {
|
|
/* 'XXXXXX' is kind of tag to be replaced by mktemp-familly by an uuid. */
|
|
char *tmp_name = BLI_strdupcat(fullname, "blender_XXXXXX");
|
|
const size_t ln = strlen(tmp_name) + 1;
|
|
if (ln <= maxlen) {
|
|
#ifdef WIN32
|
|
if (_mktemp_s(tmp_name, ln) == 0) {
|
|
BLI_dir_create_recursive(tmp_name);
|
|
}
|
|
#else
|
|
if (mkdtemp(tmp_name) == NULL) {
|
|
BLI_dir_create_recursive(tmp_name);
|
|
}
|
|
#endif
|
|
}
|
|
if (BLI_is_dir(tmp_name)) {
|
|
BLI_strncpy(basename, fullname, maxlen);
|
|
BLI_strncpy(fullname, tmp_name, maxlen);
|
|
BLI_add_slash(fullname);
|
|
}
|
|
else {
|
|
printf("Warning! Could not generate a temp file name for '%s', falling back to '%s'\n", tmp_name, fullname);
|
|
}
|
|
|
|
MEM_freeN(tmp_name);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets btempdir_base to userdir if specified and is a valid directory, otherwise
|
|
* chooses a suitable OS-specific temporary directory.
|
|
* Sets btempdir_session to a mkdtemp-generated sub-dir of btempdir_base.
|
|
*
|
|
* \note On Window userdir will be set to the temporary directory!
|
|
*/
|
|
void BKE_tempdir_init(char *userdir)
|
|
{
|
|
where_is_temp(btempdir_session, btempdir_base, FILE_MAX, userdir);
|
|
}
|
|
|
|
/**
|
|
* Path to temporary directory (with trailing slash)
|
|
*/
|
|
const char *BKE_tempdir_session(void)
|
|
{
|
|
return btempdir_session[0] ? btempdir_session : BKE_tempdir_base();
|
|
}
|
|
|
|
/**
|
|
* Path to persistent temporary directory (with trailing slash)
|
|
*/
|
|
const char *BKE_tempdir_base(void)
|
|
{
|
|
return btempdir_base;
|
|
}
|
|
|
|
/**
|
|
* Path to the system temporary directory (with trailing slash)
|
|
*/
|
|
void BKE_tempdir_system_init(char *dir)
|
|
{
|
|
where_is_temp(dir, NULL, FILE_MAX, NULL);
|
|
}
|
|
|
|
/**
|
|
* Delete content of this instance's temp dir.
|
|
*/
|
|
void BKE_tempdir_session_purge(void)
|
|
{
|
|
if (btempdir_session[0] && BLI_is_dir(btempdir_session)) {
|
|
BLI_delete(btempdir_session, true, true);
|
|
}
|
|
}
|