diff --git a/CMakeLists.txt b/CMakeLists.txt index 03f7e46ff7..4602e09996 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1104,6 +1104,8 @@ if(SDL_LIBC) check_symbol_exists(poll "poll.h" HAVE_POLL) check_symbol_exists(memfd_create "sys/mman.h" HAVE_MEMFD_CREATE) check_symbol_exists(posix_fallocate "fcntl.h" HAVE_POSIX_FALLOCATE) + check_symbol_exists(posix_spawn_file_actions_addchdir "spawn.h" HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) + check_symbol_exists(posix_spawn_file_actions_addchdir_np "spawn.h" HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) if(SDL_SYSTEM_ICONV) check_c_source_compiles(" diff --git a/include/SDL3/SDL_process.h b/include/SDL3/SDL_process.h index 511b2f9c51..870697bd7a 100644 --- a/include/SDL3/SDL_process.h +++ b/include/SDL3/SDL_process.h @@ -166,6 +166,7 @@ typedef enum SDL_ProcessIO * - `SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER`: an SDL_Environment * pointer. If this property is set, it will be the entire environment for * the process, otherwise the current environment is used. + * - `SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING`: a UTF-8 encoded string representing the working directory for the process, defaults to the current working directory. * - `SDL_PROP_PROCESS_CREATE_STDIN_NUMBER`: an SDL_ProcessIO value describing * where standard input for the process comes from, defaults to * `SDL_PROCESS_STDIO_NULL`. @@ -219,6 +220,7 @@ extern SDL_DECLSPEC SDL_Process * SDLCALL SDL_CreateProcessWithProperties(SDL_Pr #define SDL_PROP_PROCESS_CREATE_ARGS_POINTER "SDL.process.create.args" #define SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER "SDL.process.create.environment" +#define SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING "SDL.process.create.working_directory" #define SDL_PROP_PROCESS_CREATE_STDIN_NUMBER "SDL.process.create.stdin_option" #define SDL_PROP_PROCESS_CREATE_STDIN_POINTER "SDL.process.create.stdin_source" #define SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER "SDL.process.create.stdout_option" diff --git a/include/build_config/SDL_build_config.h.cmake b/include/build_config/SDL_build_config.h.cmake index 4b40e281c5..c8c02894a1 100644 --- a/include/build_config/SDL_build_config.h.cmake +++ b/include/build_config/SDL_build_config.h.cmake @@ -228,6 +228,8 @@ #cmakedefine HAVE_SHOBJIDL_CORE_H 1 #cmakedefine USE_POSIX_SPAWN 1 +#cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR 1 +#cmakedefine HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP 1 /* SDL internal assertion support */ #cmakedefine SDL_DEFAULT_ASSERT_LEVEL_CONFIGURED 1 diff --git a/src/process/posix/SDL_posixprocess.c b/src/process/posix/SDL_posixprocess.c index 98b9f75bca..2e05b01324 100644 --- a/src/process/posix/SDL_posixprocess.c +++ b/src/process/posix/SDL_posixprocess.c @@ -37,6 +37,12 @@ #include "../../io/SDL_iostream_c.h" +#if defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP) && \ + !defined(HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR) +#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR +#define posix_spawn_file_actions_addchdir posix_spawn_file_actions_addchdir_np +#endif + #define READ_END 0 #define WRITE_END 1 @@ -156,6 +162,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); char **envp = NULL; + const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED); SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED); @@ -192,6 +199,30 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID goto posix_spawn_fail_attr; } + if (working_directory) { +#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR +#ifdef SDL_PLATFORM_APPLE + if (__builtin_available(macOS 10.15, *)) { + if (posix_spawn_file_actions_addchdir(&fa, working_directory) != 0) { + SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); + goto posix_spawn_fail_all; + } + } else { + SDL_SetError("Setting the working directory is only supported on macOS 10.15 and newer"); + goto posix_spawn_fail_all; + } +#else + if (posix_spawn_file_actions_addchdir(&fa, working_directory) != 0) { + SDL_SetError("posix_spawn_file_actions_addchdir failed: %s", strerror(errno)); + goto posix_spawn_fail_all; + } +#endif // SDL_PLATFORM_APPLE +#else + SDL_SetError("Setting the working directory is not supported"); + goto posix_spawn_fail_all; +#endif + } + // Background processes don't have access to the terminal if (process->background) { if (stdin_option == SDL_PROCESS_STDIO_INHERITED) { diff --git a/src/process/windows/SDL_windowsprocess.c b/src/process/windows/SDL_windowsprocess.c index c1aee5c4ab..95f45c08f1 100644 --- a/src/process/windows/SDL_windowsprocess.c +++ b/src/process/windows/SDL_windowsprocess.c @@ -239,6 +239,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID const char * const *args = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ARGS_POINTER, NULL); SDL_Environment *env = SDL_GetPointerProperty(props, SDL_PROP_PROCESS_CREATE_ENVIRONMENT_POINTER, SDL_GetEnvironment()); char **envp = NULL; + const char *working_directory = SDL_GetStringProperty(props, SDL_PROP_PROCESS_CREATE_WORKING_DIRECTORY_STRING, NULL); SDL_ProcessIO stdin_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDIN_NUMBER, SDL_PROCESS_STDIO_NULL); SDL_ProcessIO stdout_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDOUT_NUMBER, SDL_PROCESS_STDIO_INHERITED); SDL_ProcessIO stderr_option = (SDL_ProcessIO)SDL_GetNumberProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER, SDL_PROCESS_STDIO_INHERITED); @@ -246,6 +247,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID !SDL_HasProperty(props, SDL_PROP_PROCESS_CREATE_STDERR_NUMBER); LPWSTR createprocess_cmdline = NULL; LPWSTR createprocess_env = NULL; + LPWSTR createprocess_cwd = NULL; STARTUPINFOW startup_info; DWORD creation_flags; SECURITY_ATTRIBUTES security_attributes; @@ -292,6 +294,13 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID goto done; } + if (working_directory) { + createprocess_cwd = WIN_UTF8ToStringW(working_directory); + if (!createprocess_cwd) { + goto done; + } + } + // Background processes don't have access to the terminal // This isn't necessary on Windows, but we keep the same behavior as the POSIX implementation. if (process->background) { @@ -427,7 +436,7 @@ bool SDL_SYS_CreateProcessWithProperties(SDL_Process *process, SDL_PropertiesID } } - if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, NULL, &startup_info, &data->process_information)) { + if (!CreateProcessW(NULL, createprocess_cmdline, NULL, NULL, TRUE, creation_flags, createprocess_env, createprocess_cwd, &startup_info, &data->process_information)) { WIN_SetError("CreateProcess"); goto done; } @@ -479,6 +488,7 @@ done: } SDL_free(createprocess_cmdline); SDL_free(createprocess_env); + SDL_free(createprocess_cwd); SDL_free(envp); if (!result) {