This commit is contained in:
moe li 2026-06-27 15:27:33 -04:00 committed by GitHub
commit 51ac5709e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 636 additions and 0 deletions

View File

@ -963,6 +963,29 @@ typedef enum {
NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles NPATCH_THREE_PATCH_HORIZONTAL // Npatch layout: 3x1 tiles
} NPatchLayout; } NPatchLayout;
// Opaque structs declaration for Process
// NOTE: actual definition in rcore.c
typedef struct rProcessData rProcessData;
// Process resources
typedef struct Process {
int pid; // Process unique identifier
rProcessData *processData; // Platform-specific process data
} Process;
// Process state
typedef enum {
PROCESS_STATE_NONE = 0, // No state information
PROCESS_STATE_RUNNING, // Process is running
PROCESS_STATE_FINISHED // Process has exited
} ProcessState;
// Process information for CheckProcess() function
typedef struct ProcessInfo {
ProcessState state; // Process state (PROCESS_STATE_*)
int exitCode; // Process exit code (if finished)
} ProcessInfo;
// Callbacks to hook some internal functions // Callbacks to hook some internal functions
// WARNING: These callbacks are intended for advanced users // WARNING: These callbacks are intended for advanced users
typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages typedef void (*TraceLogCallback)(int logLevel, const char *text, va_list args); // Logging: Redirect trace log messages
@ -1242,6 +1265,15 @@ RLAPI Vector2 GetTouchPosition(int index); // Get touch posit
RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index RLAPI int GetTouchPointId(int index); // Get touch point identifier for given index
RLAPI int GetTouchPointCount(void); // Get number of touch points RLAPI int GetTouchPointCount(void); // Get number of touch points
// Process execution functions
RLAPI Process InitProcess(const char *command, char *const args[]); // Initialize a new process, returns a Process struct
RLAPI ProcessInfo CheckProcess(Process process); // Check if a process is still running
RLAPI int WaitProcess(Process process); // Wait for process to finish, returns exit code, blocking
RLAPI const char *ReadProcessOutput(Process process, int *length); // Read process output as raw buffer (not null-terminated), returns pointer to a static buffer, so data must be copied before next call to ReadProcessOutput()
RLAPI void PauseProcess(Process process); // Pause process execution
RLAPI void ResumeProcess(Process process); // Resume paused process execution
RLAPI void CloseProcess(Process process); // Close process and free resources
//------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------
// Gestures and Touch Handling Functions (Module: rgestures) // Gestures and Touch Handling Functions (Module: rgestures)
//------------------------------------------------------------------------------------ //------------------------------------------------------------------------------------

View File

@ -210,6 +210,87 @@
#define ACCESS(fn) access(fn, F_OK) #define ACCESS(fn) access(fn, F_OK)
#endif #endif
// Platform specific defines for process execution API
#if defined(_WIN32)
struct _STARTUPINFOA {
unsigned long cb;
char *lpReserved;
char *lpDesktop;
char *lpTitle;
unsigned long dwX;
unsigned long dwY;
unsigned long dwXSize;
unsigned long dwYSize;
unsigned long dwXCountChars;
unsigned long dwYCountChars;
unsigned long dwFillAttribute;
unsigned long dwFlags;
unsigned short wShowWindow;
unsigned short cbReserved2;
unsigned char *lpReserved2;
void *hStdInput;
void *hStdOutput;
void *hStdError;
};
struct _SECURITY_ATTRIBUTES {
unsigned long nLength;
void *lpSecurityDescriptor;
int bInheritHandle;
};
struct _PROCESS_INFORMATION {
void *hProcess;
void *hThread;
unsigned long dwProcessId;
unsigned long dwThreadId;
};
__declspec(dllimport) void *__stdcall GetModuleHandleA(const char *lpModuleName);
__declspec(dllimport) void *__stdcall GetProcAddress(void *hModule, const char *lpProcName);
__declspec(dllimport) void *__stdcall OpenProcess(unsigned long dwDesiredAccess, int bInheritHandle, unsigned long dwProcessId);
__declspec(dllimport) int __stdcall CloseHandle(void *hObject);
__declspec(dllimport) int __stdcall GetExitCodeProcess(void *hProcess, unsigned long *lpExitCode);
__declspec(dllimport) int __stdcall TerminateProcess(void *hProcess, unsigned int uExitCode);
__declspec(dllimport) unsigned long __stdcall WaitForSingleObject(void *hHandle, unsigned long dwMilliseconds);
__declspec(dllimport) unsigned long __stdcall SuspendThread(void *hThread);
__declspec(dllimport) unsigned long __stdcall ResumeThread(void *hThread);
__declspec(dllimport) int __stdcall CreateProcessA(
const char *lpApplicationName,
char *lpCommandLine,
void *lpProcessAttributes,
void *lpThreadAttributes,
int bInheritHandles,
unsigned long dwCreationFlags,
void *lpEnvironment,
const char *lpCurrentDirectory,
struct _STARTUPINFOA *lpStartupInfo,
struct _PROCESS_INFORMATION *lpProcessInformation);
__declspec(dllimport) int __stdcall CreatePipe(
void **hReadPipe,
void **hWritePipe,
struct _SECURITY_ATTRIBUTES *lpPipeAttributes,
unsigned long nSize);
__declspec(dllimport) int __stdcall PeekNamedPipe(
void *hNamedPipe,
void *lpBuffer,
unsigned long nBufferSize,
unsigned long *lpBytesRead,
unsigned long *lpTotalBytesAvail,
unsigned long *lpBytesLeftThisMessage);
__declspec(dllimport) int __stdcall ReadFile(
void *hFile,
void *lpBuffer,
unsigned long nNumberOfBytesToRead,
unsigned long *lpNumberOfBytesRead,
void *lpOverlapped);
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
#include <sys/wait.h> // Required for: waitpid() [Used in CheckProcess()]
#include <signal.h> // Required for: kill() [Used in PauseProcess(), ResumeProcess(), CloseProcess()]
#include <sys/types.h> // Required for: pid_t [Used as function local variable type and for Process PID type]
#include <poll.h> // Required for: pollfd, poll() [Used in ReadProcessOutput()]
#endif
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Defines and Macros // Defines and Macros
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@ -4215,6 +4296,529 @@ int GetTouchPointCount(void)
return CORE.Input.Touch.pointCount; return CORE.Input.Touch.pointCount;
} }
//----------------------------------------------------------------------------------
// Module Functions Definition: Process Execution
//----------------------------------------------------------------------------------
#if defined(_WIN32)
struct rProcessData {
void *pipeStdoutRead; // Process stdout and stderr read pipe
void *pipeStdinWrite; // Process stdin write pipe
};
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
struct rProcessData {
int pipefdStdout[2]; // Process stdout and stderr pipe file descriptors
int pipefdStdin[2]; // Process stdin pipe file descriptors
};
#endif
// Initialize a new process, returns a Process struct
RLAPI Process InitProcess(const char *command, char *const args[])
{
// The last element of the args array must be NULL
Process process = { 0 };
#if defined(_WIN32)
// Windows requires a single command line string
char cmdLine[MAX_PATH * 4] = { 0 };
int position = 0;
TextAppend(cmdLine, command, &position);
if (args != NULL)
{
for (int i = 1; args[i] != NULL; i++)
{
// TODO: Maybe use dynamic allocation for command line instead of aborting?
if (position + TextLength(args[i]) + 1 >= sizeof(cmdLine))
{
TRACELOG(LOG_WARNING, "PROCESS: Command line too long, process will not be created");
return process;
}
TextAppend(cmdLine, " ", &position);
TextAppend(cmdLine, args[i], &position);
}
}
// For stdout/stderr, read handles reserved for Raylib side, write handles passed to child process
// For stdin, write handle reserved for Raylib side, read handle passed to child process
// NOTE: stdout and stderr are merged into a single pipe
void *pipeStdoutRead = NULL, *pipeStdoutWrite = NULL;
void *pipeStdinRead = NULL, *pipeStdinWrite = NULL;
struct _SECURITY_ATTRIBUTES sa = { 0 };
sa.nLength = sizeof(struct _SECURITY_ATTRIBUTES);
sa.bInheritHandle = 1;
sa.lpSecurityDescriptor = NULL;
if ((!CreatePipe(&pipeStdoutRead, &pipeStdoutWrite, &sa, 0)) ||
(!CreatePipe(&pipeStdinRead, &pipeStdinWrite, &sa, 0)))
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to create pipes for process I/O redirection");
void *pipes[] = { pipeStdoutRead, pipeStdoutWrite, pipeStdinRead, pipeStdinWrite };
for (int i = 0; i < 4; i++) {
if (pipes[i] != NULL) CloseHandle(pipes[i]);
}
return process;
}
struct _STARTUPINFOA si = { 0 };
si.cb = sizeof(struct _STARTUPINFOA);
// STARTF_USESTDHANDLES
si.dwFlags = 0x00000100;
si.hStdError = pipeStdoutWrite;
si.hStdOutput = pipeStdoutWrite;
si.hStdInput = pipeStdinRead;
struct _PROCESS_INFORMATION pi = { 0 };
// Application name is specified as the first command line argument, so pass NULL to CreateProcessA here
// Flag bInheritHandles is set to TRUE, so child process will have access to the pipes
if (CreateProcessA(NULL, cmdLine, NULL, NULL, 1, 0, NULL, NULL, &si, &pi))
{
process.pid = (int)pi.dwProcessId;
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
process.processData = (rProcessData *)RL_MALLOC(sizeof(rProcessData));
process.processData->pipeStdoutRead = pipeStdoutRead;
process.processData->pipeStdinWrite = pipeStdinWrite;
// Free parent process handles
CloseHandle(pipeStdoutWrite);
CloseHandle(pipeStdinRead);
}
else
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to create process");
}
return process;
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
// Initialize fd to -1 for error checking
int pipefd[4] = { -1, -1, -1, -1 };
if ((pipe(&pipefd[0]) == -1) ||
(pipe(&pipefd[2]) == -1))
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to create pipes");
for (int i = 0; i < 4; i++) {
if (pipefd[i] != -1) close(pipefd[i]);
}
return process;
}
process.processData = (rProcessData *)RL_MALLOC(sizeof(rProcessData));
memcpy(process.processData->pipefdStdout, &pipefd[0], sizeof(int) * 2);
memcpy(process.processData->pipefdStdin, &pipefd[2], sizeof(int) * 2);
pid_t pid = fork();
if (pid < 0)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to create process");
for (int i = 0; i < 4; i++) close(pipefd[i]);
RL_FREE(process.processData);
return process;
}
else if (pid == 0)
{
// Child process
// Close pipe read end, redirect stdout and stderr to pipe write end
close(process.processData->pipefdStdout[0]);
dup2(process.processData->pipefdStdout[1], STDOUT_FILENO);
dup2(process.processData->pipefdStdout[1], STDERR_FILENO);
close(process.processData->pipefdStdout[1]);
// Close pipe write end, redirect stdin to pipe read end
close(process.processData->pipefdStdin[1]);
dup2(process.processData->pipefdStdin[0], STDIN_FILENO);
close(process.processData->pipefdStdin[0]);
// Execute command
execvp(command, args);
// If execvp returns, it must have failed
_exit(1);
}
else
{
// Parent process
process.pid = pid;
// Close stdout pipe write end and stdin pipe read end
close(process.processData->pipefdStdout[1]);
close(process.processData->pipefdStdin[0]);
}
return process;
#else
TRACELOG(LOG_WARNING, "PROCESS: Process management not supported on this platform");
return process;
#endif
}
// Check if a process is still running, updates Process struct
RLAPI ProcessInfo CheckProcess(Process process)
{
ProcessInfo info = { 0 };
if (process.pid <= 0)
{
info.state = PROCESS_STATE_NONE;
return info;
}
#if defined(_WIN32)
// PROCESS_QUERY_INFORMATION
void *hProcess = OpenProcess(0x0400, 0, (unsigned long)process.pid);
if (hProcess == NULL)
{
// Process does not exist
info.state = PROCESS_STATE_NONE;
return info;
}
unsigned long exitCode = 0;
if (GetExitCodeProcess(hProcess, &exitCode))
{
CloseHandle(hProcess);
// STILL_ACTIVE (259) means the process is still running
if (exitCode == 259)
{
info.state = PROCESS_STATE_RUNNING;
return info;
}
info.state = PROCESS_STATE_FINISHED;
info.exitCode = (int)exitCode;
return info;
}
CloseHandle(hProcess);
// If GetExitCodeProcess fails, we consider the process state as unknown
info.state = PROCESS_STATE_NONE;
return info;
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
int status = 0;
pid_t result = waitpid(process.pid, &status, WNOHANG);
if (result == 0)
{
// Still running
info.state = PROCESS_STATE_RUNNING;
return info;
}
else if (result == process.pid)
{
// Not running
if (WIFEXITED(status))
{
// Normal exit
info.state = PROCESS_STATE_FINISHED;
info.exitCode = WEXITSTATUS(status);
}
else if (WIFSIGNALED(status))
{
// Terminated by signal
info.state = PROCESS_STATE_FINISHED;
info.exitCode = -1;
}
return info;
}
else
{
// Error occurred
info.state = PROCESS_STATE_NONE;
return info;
}
#else
TRACELOG(LOG_WARNING, "PROCESS: Process management not supported on this platform");
#endif
return info;
}
RLAPI int WaitProcess(Process process)
{
if (process.pid <= 0)
{
return -1;
}
#if defined(_WIN32)
// PROCESS_QUERY_INFORMATION | SYNCHRONIZE
// SYNCHRONIZE for WaitForSingleObject
void *hProcess = OpenProcess(0x0400 | 0x00100000, 0, (unsigned long)process.pid);
if (hProcess == NULL)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to open process handle");
return -1;
}
// Wait indefinitely for process termination
// NOTE: calling WaitProcess while trying to read process output may cause deadlock
// INFINITE
WaitForSingleObject(hProcess, 0xFFFFFFFF);
// Get exit code
unsigned long exitCode = 0;
if (GetExitCodeProcess(hProcess, &exitCode))
{
CloseHandle(hProcess);
return (int)exitCode;
}
CloseHandle(hProcess);
return -1;
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
int status = 0;
pid_t result = waitpid(process.pid, &status, 0);
if (result == process.pid)
{
if (WIFEXITED(status))
{
// Normal exit
return WEXITSTATUS(status);
}
// Terminated by signal or other non-normal exit
return -1;
}
else
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to wait for process");
return -1;
}
#else
TRACELOG(LOG_WARNING, "PROCESS: Process management not supported on this platform");
return -1;
#endif
}
RLAPI const char *ReadProcessOutput(Process process, int *length)
{
if (process.pid <= 0)
{
return NULL;
}
#if defined(_WIN32)
static char output[4096] = { 0 };
// Read from output pipe
// ReadFile will block if there is no data, so call PeekNamedPipe to check for data before reading
unsigned long bytesAvail = 0;
if ((!PeekNamedPipe(process.processData->pipeStdoutRead, NULL, 0, NULL, &bytesAvail, NULL)) ||
(bytesAvail == 0))
{
*length = 0;
return NULL;
}
unsigned long bytesRead = 0;
if (!ReadFile(process.processData->pipeStdoutRead, output, sizeof(output), &bytesRead, NULL))
{
*length = 0;
return NULL;
}
*length = (int)bytesRead;
return output;
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
static char output[4096] = { 0 };
struct pollfd fds[1];
fds[0].fd = process.processData->pipefdStdout[0];
fds[0].events = POLLIN;
// No timeout, just check if there is data to read
int pollResult = poll(fds, 1, 0);
if ((pollResult > 0) && (fds[0].revents & POLLIN))
{
ssize_t bytesRead = read(process.processData->pipefdStdout[0], output, sizeof(output));
if (bytesRead > 0)
{
*length = (int)bytesRead;
return output;
}
}
*length = 0;
return NULL;
#else
TRACELOG(LOG_WARNING, "PROCESS: Process management not supported on this platform");
return NULL;
#endif
}
// Pause process execution
RLAPI void PauseProcess(Process process)
{
if (process.pid <= 0)
{
return;
}
#if defined(_WIN32)
typedef long (*PFN_NtSuspendProcess)(void *handle);
static PFN_NtSuspendProcess NtSuspendProcess = NULL;
// Load NtSuspendProcess from ntdll.dll
if (NtSuspendProcess == NULL)
{
void *hNtDll = GetModuleHandleA("ntdll.dll");
if (hNtDll == NULL)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to get handle for ntdll.dll");
return;
}
NtSuspendProcess = (PFN_NtSuspendProcess)GetProcAddress(hNtDll, "NtSuspendProcess");
if (NtSuspendProcess == NULL) {
TRACELOG(LOG_WARNING, "PROCESS: Failed to get address for NtSuspendProcess");
return;
}
}
// PROCESS_SUSPEND_RESUME
void *hProcess = OpenProcess(0x0800, 0, (unsigned long)process.pid);
if (hProcess == NULL) {
TRACELOG(LOG_WARNING, "PROCESS: Failed to open process handle");
return;
}
if (NtSuspendProcess(hProcess) != 0)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to pause process");
}
CloseHandle(hProcess);
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
if (kill(process.pid, SIGSTOP) != 0)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to pause process");
}
#else
TRACELOG(LOG_WARNING, "PROCESS: Process management not supported on this platform");
#endif
}
// Resume paused process execution
RLAPI void ResumeProcess(Process process)
{
if (process.pid <= 0)
{
return;
}
#if defined(_WIN32)
typedef long (*PFN_NtResumeProcess)(void *handle);
static PFN_NtResumeProcess NtResumeProcess = NULL;
// Load NtResumeProcess from ntdll.dll
if (NtResumeProcess == NULL)
{
void *hNtDll = GetModuleHandleA("ntdll.dll");
if (hNtDll == NULL)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to get handle for ntdll.dll");
return;
}
NtResumeProcess = (PFN_NtResumeProcess)GetProcAddress(hNtDll, "NtResumeProcess");
if (NtResumeProcess == NULL)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to get address for NtResumeProcess");
return;
}
}
// PROCESS_SUSPEND_RESUME
void *hProcess = OpenProcess(0x0800, 0, (unsigned long)process.pid);
if (hProcess == NULL)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to open process handle");
return;
}
if (NtResumeProcess(hProcess) != 0)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to resume process");
}
CloseHandle(hProcess);
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
if (kill(process.pid, SIGCONT) != 0)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to resume process");
}
#else
TRACELOG(LOG_WARNING, "PROCESS: Process management not supported on this platform");
#endif
}
// Close process and free resources
RLAPI void CloseProcess(Process process)
{
if (process.pid <= 0)
{
return;
}
#if defined(_WIN32)
// PROCESS_TERMINATE
void *hProcess = OpenProcess(0x0001, 0, (unsigned long)process.pid);
if (hProcess == NULL)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to open process handle");
return;
}
TerminateProcess(hProcess, 0);
CloseHandle(hProcess);
// Clean up pipes
if (process.processData->pipeStdoutRead != NULL) CloseHandle(process.processData->pipeStdoutRead);
if (process.processData->pipeStdinWrite != NULL) CloseHandle(process.processData->pipeStdinWrite);
RL_FREE(process.processData);
#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__)
// If process is not running or not found, nothing to do
ProcessInfo info = CheckProcess(process);
if (info.state != PROCESS_STATE_RUNNING)
{
return;
}
// In case that the process is paused
kill(process.pid, SIGCONT);
if (kill(process.pid, SIGTERM) != 0)
{
TRACELOG(LOG_WARNING, "PROCESS: Failed to terminate process");
// Kill the process anyway
kill(process.pid, SIGKILL);
}
// Wait process termination
waitpid(process.pid, NULL, 0);
close(process.processData->pipefdStdout[0]);
close(process.processData->pipefdStdin[1]);
RL_FREE(process.processData);
#else
TRACELOG(LOG_WARNING, "PROCESS: Process management not supported on this platform");
#endif
}
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
// Module Internal Functions Definition // Module Internal Functions Definition
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------