Refactor: extract platform abstraction for subprocess spawning
Move all Unix-specific syscalls (fork, execvp, waitpid, strerror, /tmp) into a platform layer so the same spectrogram.c can target Windows (CreateProcess) and WebAssembly (stubs). Key changes: - src/platform.h: public API with spawn handle, error codes, path helpers - src/platform_linux.c: fork/execvp implementation, /tmp, strerror - src/platform_win32.c: CreateProcess implementation, temp dir lookup - src/platform_web.c: stub implementations (no subprocess support on web) - src/spectrogram.c: consume only platform.h; replace pid_t/fork/execvp with Platform_SpawnChild/WaitForChild; use Platform_GetTempDir() for /tmp path; replace strdup with malloc/memcpy for portability - Build: add platform_*.c to gmake, Premake, and build_web.sh
This commit is contained in:
@@ -202,6 +202,19 @@ if (downloadRaylib) then
|
|||||||
files {"../src/*.rc", "../src/*.ico"}
|
files {"../src/*.rc", "../src/*.ico"}
|
||||||
files {"../resources/**"}
|
files {"../resources/**"}
|
||||||
|
|
||||||
|
-- Exclude platform-specific files on each target
|
||||||
|
filter {"system:linux"}
|
||||||
|
excludedfiles { "../src/platform_win32.c", "../src/platform_web.c" }
|
||||||
|
|
||||||
|
filter {"system:windows"}
|
||||||
|
excludedfiles { "../src/platform_linux.c", "../src/platform_web.c" }
|
||||||
|
|
||||||
|
filter {"system:macosx"}
|
||||||
|
excludedfiles { "../src/platform_win32.c", "../src/platform_web.c" }
|
||||||
|
|
||||||
|
filter {"action:emscripten"}
|
||||||
|
excludedfiles { "../src/platform_linux.c", "../src/platform_win32.c" }
|
||||||
|
|
||||||
filter{}
|
filter{}
|
||||||
|
|
||||||
includedirs { "../src" }
|
includedirs { "../src" }
|
||||||
|
|||||||
+16
-2
@@ -42,9 +42,22 @@ cd "$RAYLIB_BUILD"
|
|||||||
emar rcs libraylib.a rcore.o rshapes.o rtextures.o rtext.o rmodels.o raudio.o
|
emar rcs libraylib.a rcore.o rshapes.o rtextures.o rtext.o rmodels.o raudio.o
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
echo "=== Step 2: Compiling spectrogram for web ==="
|
# Compile spectrogram (web version — no subprocess support)
|
||||||
|
# platform_web.c provides stub implementations for spawn/error functions
|
||||||
|
echo "=== Step 2: Compiling spectrogram + platform for web ==="
|
||||||
cd "$SCRIPT_DIR"
|
cd "$SCRIPT_DIR"
|
||||||
|
|
||||||
|
# Compile platform_web.o (handles fork/execvp stubs for web)
|
||||||
|
emcc -c -Os -Wall -DPLATFORM_WEB "$SRC_DIR/platform_web.c" -o "$RAYLIB_BUILD/platform_web.o"
|
||||||
|
|
||||||
|
# Compile spectrogram
|
||||||
|
emcc -c -Os -Wall -DPLATFORM_WEB "$SRC_DIR/spectrogram.c" \
|
||||||
|
-I"$RAYLIB_DIR/src" \
|
||||||
|
-I"$RAYLIB_DIR/src/external" \
|
||||||
|
-I"$RAYLIB_DIR/src/external/glfw/include" \
|
||||||
|
-Iinclude \
|
||||||
|
-o "$RAYLIB_BUILD/spectrogram.o"
|
||||||
|
|
||||||
# Linker flags
|
# Linker flags
|
||||||
LDFLAGS="-s USE_GLFW=3 -s ASYNCIFY -s TOTAL_MEMORY=67108864 -s FORCE_FILESYSTEM=1 --preload-file resources@resources --preload-file fonts/DejaVuSansMono.ttf@fonts/DejaVuSansMono.ttf --shell-file $SCRIPT_DIR/web_shell.html -s NO_EXIT_RUNTIME=1"
|
LDFLAGS="-s USE_GLFW=3 -s ASYNCIFY -s TOTAL_MEMORY=67108864 -s FORCE_FILESYSTEM=1 --preload-file resources@resources --preload-file fonts/DejaVuSansMono.ttf@fonts/DejaVuSansMono.ttf --shell-file $SCRIPT_DIR/web_shell.html -s NO_EXIT_RUNTIME=1"
|
||||||
|
|
||||||
@@ -56,7 +69,8 @@ fi
|
|||||||
|
|
||||||
# Compile and link
|
# Compile and link
|
||||||
emcc -o "$OUTPUT" \
|
emcc -o "$OUTPUT" \
|
||||||
"$SRC_DIR/spectrogram.c" \
|
"$RAYLIB_BUILD/platform_web.o" \
|
||||||
|
"$RAYLIB_BUILD/spectrogram.o" \
|
||||||
-I"$RAYLIB_DIR/src" \
|
-I"$RAYLIB_DIR/src" \
|
||||||
-I"$RAYLIB_DIR/src/external" \
|
-I"$RAYLIB_DIR/src/external" \
|
||||||
-I"$RAYLIB_DIR/src/external/glfw/include" \
|
-I"$RAYLIB_DIR/src/external/glfw/include" \
|
||||||
|
|||||||
@@ -119,7 +119,9 @@ GENERATED :=
|
|||||||
OBJECTS :=
|
OBJECTS :=
|
||||||
|
|
||||||
GENERATED += $(OBJDIR)/spectrogram.o
|
GENERATED += $(OBJDIR)/spectrogram.o
|
||||||
|
GENERATED += $(OBJDIR)/platform_linux.o
|
||||||
OBJECTS += $(OBJDIR)/spectrogram.o
|
OBJECTS += $(OBJDIR)/spectrogram.o
|
||||||
|
OBJECTS += $(OBJDIR)/platform_linux.o
|
||||||
|
|
||||||
# Rules
|
# Rules
|
||||||
# #############################################
|
# #############################################
|
||||||
@@ -187,6 +189,10 @@ $(OBJDIR)/spectrogram.o: src/spectrogram.c
|
|||||||
@echo "$(notdir $<)"
|
@echo "$(notdir $<)"
|
||||||
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||||
|
|
||||||
|
$(OBJDIR)/platform_linux.o: src/platform_linux.c
|
||||||
|
@echo "$(notdir $<)"
|
||||||
|
$(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<"
|
||||||
|
|
||||||
-include $(OBJECTS:%.o=%.d)
|
-include $(OBJECTS:%.o=%.d)
|
||||||
ifneq (,$(PCH))
|
ifneq (,$(PCH))
|
||||||
-include $(PCH_PLACEHOLDER).d
|
-include $(PCH_PLACEHOLDER).d
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/* ── Public API ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Platform identification (for build-time selection).
|
||||||
|
* Only one should be defined: PLATFORM_LINUX, PLATFORM_WIN32, PLATFORM_WEB.
|
||||||
|
* Undefined means unknown / desktop fallback.
|
||||||
|
*/
|
||||||
|
enum Platform {
|
||||||
|
PLATFORM_UNKNOWN = 0,
|
||||||
|
PLATFORM_LINUX,
|
||||||
|
PLATFORM_WIN32,
|
||||||
|
PLATFORM_WEB,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error codes returned by platform functions.
|
||||||
|
*/
|
||||||
|
enum PlatformError {
|
||||||
|
PLATFORM_OK = 0, /**< Success */
|
||||||
|
PLATFORM_GENERAL = -1, /**< Non-specific failure */
|
||||||
|
PLATFORM_NOT_SUPPORTED = -2, /**< Function not available on this platform */
|
||||||
|
};
|
||||||
|
typedef int PlatformError;
|
||||||
|
|
||||||
|
/* ── Process / spawning ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of waiting for a spawned process.
|
||||||
|
*/
|
||||||
|
enum SpawnStatus {
|
||||||
|
SPAWN_WAITING, /**< Process still running */
|
||||||
|
SPAWN_EXITED, /**< Process has exited, exitCode is valid */
|
||||||
|
SPAWN_EXIT_CODE_UNKNOWN, /**< Exited but we could not determine code */
|
||||||
|
};
|
||||||
|
typedef int SpawnStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opaque handle to a spawned child process.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
void *handle;
|
||||||
|
enum SpawnStatus status;
|
||||||
|
int exit_code;
|
||||||
|
int last_error;
|
||||||
|
} PlatformSpawnHandle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a child process synchronously. Blocks until the child
|
||||||
|
* exits — without going through a shell, so user-controlled
|
||||||
|
* arguments cannot inject commands.
|
||||||
|
*
|
||||||
|
* @param program Program to run (passed directly to execvp /
|
||||||
|
* CreateProcess, never split by a shell).
|
||||||
|
* @param argv NULL-terminated argument array. argv[0] is the
|
||||||
|
* program name, argv[1..n] are arguments.
|
||||||
|
* @param[out] out_handle Fill with a handle for status queries.
|
||||||
|
* @return PLATFORM_OK on success, PLATFORM_GENERAL on failure.
|
||||||
|
*/
|
||||||
|
PlatformError Platform_SpawnChild(const char *program, const char **argv,
|
||||||
|
PlatformSpawnHandle *out_handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for a spawned process to exit.
|
||||||
|
*/
|
||||||
|
PlatformError Platform_WaitForChild(PlatformSpawnHandle *handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the exit status of a spawned process.
|
||||||
|
*/
|
||||||
|
SpawnStatus Platform_GetExitStatus(const PlatformSpawnHandle *handle,
|
||||||
|
int *code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up resources associated with a spawn handle.
|
||||||
|
*/
|
||||||
|
void Platform_CloseSpawnHandle(PlatformSpawnHandle *handle);
|
||||||
|
|
||||||
|
/* ── Error description ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Human-readable description of the last platform error.
|
||||||
|
* Returns a static string (caller does not free).
|
||||||
|
*/
|
||||||
|
const char *Platform_GetLastErrorMessage(void);
|
||||||
|
|
||||||
|
/* ── Paths ──────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary directory for the platform.
|
||||||
|
* Static string — do not modify or free.
|
||||||
|
*/
|
||||||
|
const char *Platform_GetTempDir(void);
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
/* Platform layer: Linux — fork/execvp, path helpers, error strings */
|
||||||
|
|
||||||
|
#ifndef _DEFAULT_SOURCE
|
||||||
|
#define _DEFAULT_SOURCE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#define STATIC static
|
||||||
|
|
||||||
|
STATIC char last_error_msg[256] = { 0 };
|
||||||
|
STATIC PlatformSpawnHandle current_handle = { 0 };
|
||||||
|
|
||||||
|
/* ── Helpers ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
STATIC void set_last_error(const char *fmt, ...) {
|
||||||
|
// Simple snprintf to a local buffer — fine for error paths
|
||||||
|
char buf[256] = { 0 };
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
strncpy(last_error_msg, buf, sizeof(last_error_msg) - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Platform API ────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
PlatformError Platform_SpawnChild(const char *program, const char **argv,
|
||||||
|
PlatformSpawnHandle *out_handle) {
|
||||||
|
last_error_msg[0] = '\0';
|
||||||
|
|
||||||
|
memset(out_handle, 0, sizeof(*out_handle));
|
||||||
|
pid_t pid = fork();
|
||||||
|
|
||||||
|
if (pid < 0) {
|
||||||
|
snprintf(last_error_msg, sizeof(last_error_msg),
|
||||||
|
"fork() failed: %s", strerror(errno));
|
||||||
|
out_handle->last_error = errno;
|
||||||
|
return PLATFORM_GENERAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
// Child process — replace with target
|
||||||
|
execvp(program, (char **)argv);
|
||||||
|
_exit(127); // execvp only returns on error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parent
|
||||||
|
out_handle->handle = (void *)(intptr_t)pid;
|
||||||
|
out_handle->status = SPAWN_WAITING;
|
||||||
|
return PLATFORM_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformError Platform_WaitForChild(PlatformSpawnHandle *handle) {
|
||||||
|
if (!handle || !handle->handle) return PLATFORM_GENERAL;
|
||||||
|
|
||||||
|
pid_t pid = (pid_t)(intptr_t)handle->handle;
|
||||||
|
int status = 0;
|
||||||
|
waitpid(pid, &status, 0);
|
||||||
|
|
||||||
|
if (WIFEXITED(status)) {
|
||||||
|
handle->status = SPAWN_EXITED;
|
||||||
|
handle->exit_code = WEXITSTATUS(status);
|
||||||
|
} else if (WIFSIGNALED(status)) {
|
||||||
|
handle->status = SPAWN_EXITED;
|
||||||
|
handle->exit_code = WTERMSIG(status);
|
||||||
|
} else {
|
||||||
|
handle->status = SPAWN_EXIT_CODE_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->handle = NULL; // Consumed
|
||||||
|
return PLATFORM_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpawnStatus Platform_GetExitStatus(const PlatformSpawnHandle *handle, int *code) {
|
||||||
|
if (!handle) return SPAWN_WAITING;
|
||||||
|
if (code) *code = handle->exit_code;
|
||||||
|
return handle->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Platform_CloseSpawnHandle(PlatformSpawnHandle *handle) {
|
||||||
|
if (!handle) return;
|
||||||
|
handle->handle = NULL;
|
||||||
|
handle->status = SPAWN_WAITING;
|
||||||
|
handle->exit_code = 0;
|
||||||
|
handle->last_error = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Platform_GetLastErrorMessage(void) {
|
||||||
|
return last_error_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Platform_GetTempDir(void) {
|
||||||
|
return "/tmp";
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* platform_web.c — Emscripten/WebAssembly stub implementation
|
||||||
|
*
|
||||||
|
* The web version cannot spawn child processes, so these functions
|
||||||
|
* return "not supported". A real implementation would use Emscripten's
|
||||||
|
* pthreads or asyncify if ffmpeg integration is needed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
/* ── Helpers ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static char last_error_msg[256] = { 0 };
|
||||||
|
|
||||||
|
static void set_last_error(const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(last_error_msg, sizeof(last_error_msg), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Platform API ────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
PlatformError Platform_SpawnChild(const char *program, const char **argv,
|
||||||
|
PlatformSpawnHandle *out_handle) {
|
||||||
|
(void)program;
|
||||||
|
(void)argv;
|
||||||
|
set_last_error("Spawning child processes is not supported on the web");
|
||||||
|
out_handle->last_error = ENOSYS;
|
||||||
|
return PLATFORM_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformError Platform_WaitForChild(PlatformSpawnHandle *handle) {
|
||||||
|
(void)handle;
|
||||||
|
return PLATFORM_NOT_SUPPORTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpawnStatus Platform_GetExitStatus(const PlatformSpawnHandle *handle, int *code) {
|
||||||
|
(void)handle;
|
||||||
|
(void)code;
|
||||||
|
return SPAWN_WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Platform_CloseSpawnHandle(PlatformSpawnHandle *handle) {
|
||||||
|
if (!handle) return;
|
||||||
|
handle->handle = NULL;
|
||||||
|
handle->status = SPAWN_WAITING;
|
||||||
|
handle->exit_code = 0;
|
||||||
|
handle->last_error = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Platform_GetLastErrorMessage(void) {
|
||||||
|
return last_error_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Platform_GetTempDir(void) {
|
||||||
|
/*
|
||||||
|
* Emscripten's virtual filesystem: "/" works as root.
|
||||||
|
* For persistent downloads/uploads, consider IDBFS mount.
|
||||||
|
*/
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* platform_win32.c — Windows implementation
|
||||||
|
*
|
||||||
|
* Requires: Windows.h, mincore.lib (for CreateProcessA)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WIN32_LEAN_AND_MEAN
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#endif
|
||||||
|
#include <windows.h>
|
||||||
|
#include "platform.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
|
/* ── Helpers ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static char last_error_msg[256] = { 0 };
|
||||||
|
|
||||||
|
static void set_last_error(const char *fmt, ...) {
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
vsnprintf(last_error_msg, sizeof(last_error_msg), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Platform API ────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
PlatformError Platform_SpawnChild(const char *program, const char **argv,
|
||||||
|
PlatformSpawnHandle *out_handle) {
|
||||||
|
last_error_msg[0] = '\0';
|
||||||
|
|
||||||
|
/* Build a single command line from argv */
|
||||||
|
char cmdline[2048] = { 0 };
|
||||||
|
for (int i = 0; argv[i]; i++) {
|
||||||
|
if (i > 0) strncat(cmdline, " ", sizeof(cmdline) - strlen(cmdline) - 1);
|
||||||
|
/* Quote arguments that contain spaces */
|
||||||
|
if (strchr(argv[i], ' ')) {
|
||||||
|
strncat(cmdline, "\"", sizeof(cmdline) - strlen(cmdline) - 1);
|
||||||
|
strncat(cmdline, argv[i], sizeof(cmdline) - strlen(cmdline) - 1);
|
||||||
|
strncat(cmdline, "\"", sizeof(cmdline) - strlen(cmdline) - 1);
|
||||||
|
} else {
|
||||||
|
strncat(cmdline, argv[i], sizeof(cmdline) - strlen(cmdline) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CreateProcess needs a mutable buffer */
|
||||||
|
char cmd_copy[2048];
|
||||||
|
strncpy(cmd_copy, cmdline, sizeof(cmd_copy) - 1);
|
||||||
|
cmd_copy[sizeof(cmd_copy) - 1] = '\0';
|
||||||
|
|
||||||
|
STARTUPINFOA si;
|
||||||
|
ZeroMemory(&si, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
ZeroMemory(&pi, sizeof(pi));
|
||||||
|
|
||||||
|
if (!CreateProcessA(NULL, cmd_copy, NULL, NULL, FALSE,
|
||||||
|
0, NULL, NULL, &si, &pi)) {
|
||||||
|
DWORD err = GetLastError();
|
||||||
|
char msg[256];
|
||||||
|
sprintf(msg, "CreateProcess failed (code %lu)", err);
|
||||||
|
strncpy(last_error_msg, msg, sizeof(last_error_msg) - 1);
|
||||||
|
out_handle->last_error = (int)err;
|
||||||
|
return PLATFORM_GENERAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
out_handle->handle = (void *)pi.hProcess;
|
||||||
|
out_handle->status = SPAWN_WAITING;
|
||||||
|
/* Don't close the thread handle here — CloseSpawnHandle will */
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
|
||||||
|
return PLATFORM_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlatformError Platform_WaitForChild(PlatformSpawnHandle *handle) {
|
||||||
|
if (!handle || !handle->handle) return PLATFORM_GENERAL;
|
||||||
|
|
||||||
|
DWORD wait_result = WaitForSingleObject((HANDLE)handle->handle, INFINITE);
|
||||||
|
if (wait_result != WAIT_OBJECT_0) {
|
||||||
|
handle->last_error = (int)GetLastError();
|
||||||
|
return PLATFORM_GENERAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD exit_code = 0;
|
||||||
|
if (!GetExitCodeProcess((HANDLE)handle->handle, &exit_code)) {
|
||||||
|
handle->status = SPAWN_EXIT_CODE_UNKNOWN;
|
||||||
|
handle->last_error = (int)GetLastError();
|
||||||
|
return PLATFORM_GENERAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
handle->status = SPAWN_EXITED;
|
||||||
|
handle->exit_code = (int)exit_code;
|
||||||
|
handle->handle = NULL;
|
||||||
|
return PLATFORM_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpawnStatus Platform_GetExitStatus(const PlatformSpawnHandle *handle, int *code) {
|
||||||
|
if (!handle) return SPAWN_WAITING;
|
||||||
|
if (code) *code = handle->exit_code;
|
||||||
|
return handle->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Platform_CloseSpawnHandle(PlatformSpawnHandle *handle) {
|
||||||
|
if (!handle) return;
|
||||||
|
if (handle->handle) {
|
||||||
|
CloseHandle((HANDLE)handle->handle);
|
||||||
|
handle->handle = NULL;
|
||||||
|
}
|
||||||
|
handle->status = SPAWN_WAITING;
|
||||||
|
handle->exit_code = 0;
|
||||||
|
handle->last_error = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Platform_GetLastErrorMessage(void) {
|
||||||
|
return last_error_msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *Platform_GetTempDir(void) {
|
||||||
|
/* Get temp directory path once and cache it */
|
||||||
|
static char temp_path[MAX_PATH] = { 0 };
|
||||||
|
if (temp_path[0] == '\0') {
|
||||||
|
DWORD len = GetTempPathA(sizeof(temp_path), temp_path);
|
||||||
|
if (len == 0 || len >= sizeof(temp_path)) {
|
||||||
|
strncpy(temp_path, "C:\\Windows\\Temp", sizeof(temp_path) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return temp_path;
|
||||||
|
}
|
||||||
+34
-15
@@ -1,11 +1,9 @@
|
|||||||
// spectrogram.c - Spectrogram viewer with region selection and playback
|
// spectrogram.c - Spectrogram viewer with region selection and playback
|
||||||
// Based on Unity Audio-Experiments project
|
// Based on Unity Audio-Experiments project
|
||||||
|
|
||||||
#define _POSIX_C_SOURCE 200809L
|
|
||||||
#define _DEFAULT_SOURCE
|
|
||||||
|
|
||||||
#include "raylib.h"
|
#include "raylib.h"
|
||||||
#include "resource_dir.h"
|
#include "resource_dir.h"
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
@@ -418,21 +416,34 @@ static void FreeSTFT(StftResult* result)
|
|||||||
// Convert audio file to WAV using ffmpeg (if available)
|
// Convert audio file to WAV using ffmpeg (if available)
|
||||||
static bool ConvertToFFmpegWAV(const char* inputPath, char* outputPath, size_t outputSize)
|
static bool ConvertToFFmpegWAV(const char* inputPath, char* outputPath, size_t outputSize)
|
||||||
{
|
{
|
||||||
// Create temp WAV path
|
snprintf(outputPath, outputSize, "%s/rspektrum_temp_converted.wav",
|
||||||
snprintf(outputPath, outputSize, "/tmp/rspektrum_temp_converted.wav");
|
Platform_GetTempDir());
|
||||||
|
|
||||||
// Build ffmpeg command
|
/* Build argv array — no shell = no injection risk */
|
||||||
char cmd[1024];
|
const char* argv[] = {
|
||||||
snprintf(cmd, sizeof(cmd), "ffmpeg -y -loglevel quiet -i \"%s\" -ar 48000 -ac 1 -f wav \"%s\" 2>&1", inputPath, outputPath);
|
"ffmpeg", "-y", "-loglevel", "quiet",
|
||||||
|
"-i", inputPath,
|
||||||
|
"-ar", "48000", "-ac", "1", "-f", "wav",
|
||||||
|
outputPath, NULL
|
||||||
|
};
|
||||||
|
|
||||||
int result = system(cmd);
|
PlatformSpawnHandle handle = { 0 };
|
||||||
|
PlatformError rc = Platform_SpawnChild("ffmpeg", argv, &handle);
|
||||||
|
if (rc != PLATFORM_OK) {
|
||||||
|
TraceLog(LOG_WARNING, "Failed to spawn ffmpeg: %s", Platform_GetLastErrorMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (result == 0 && FileExists(outputPath)) {
|
Platform_WaitForChild(&handle);
|
||||||
|
|
||||||
|
int exit_code = 0;
|
||||||
|
SpawnStatus status = Platform_GetExitStatus(&handle, &exit_code);
|
||||||
|
if (status == SPAWN_EXITED && exit_code == 0 && FileExists(outputPath)) {
|
||||||
TraceLog(LOG_INFO, "FFmpeg conversion successful: %s", outputPath);
|
TraceLog(LOG_INFO, "FFmpeg conversion successful: %s", outputPath);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceLog(LOG_WARNING, "FFmpeg conversion failed or not available");
|
TraceLog(LOG_WARNING, "FFmpeg conversion failed (exit code %d)", exit_code);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,7 +470,7 @@ static bool LoadWavFile(const char* filepath, AudioSignal* signal)
|
|||||||
Wave wave = LoadWave(filepath);
|
Wave wave = LoadWave(filepath);
|
||||||
if (wave.data == NULL) {
|
if (wave.data == NULL) {
|
||||||
TraceLog(LOG_ERROR, "Failed to open WAV file: %s", filepath);
|
TraceLog(LOG_ERROR, "Failed to open WAV file: %s", filepath);
|
||||||
if (isConverted) remove(convertedPath);
|
if (isConverted) FileRemove(convertedPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -493,7 +504,7 @@ static bool LoadWavFile(const char* filepath, AudioSignal* signal)
|
|||||||
|
|
||||||
// Clean up temp file if converted
|
// Clean up temp file if converted
|
||||||
if (isConverted) {
|
if (isConverted) {
|
||||||
remove(convertedPath);
|
FileRemove(convertedPath);
|
||||||
TraceLog(LOG_INFO, "Cleaned up temp file: %s", convertedPath);
|
TraceLog(LOG_INFO, "Cleaned up temp file: %s", convertedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -768,11 +779,15 @@ static void ScanDirectory(const char* path)
|
|||||||
const char* name = GetFileName(files.paths[i]);
|
const char* name = GetFileName(files.paths[i]);
|
||||||
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
||||||
if (DirectoryExists(files.paths[i])) {
|
if (DirectoryExists(files.paths[i])) {
|
||||||
app.browserFiles[app.browserFileCount] = strdup(name);
|
size_t len = strlen(name) + 1;
|
||||||
|
app.browserFiles[app.browserFileCount] = (char*)malloc(len);
|
||||||
|
if (app.browserFiles[app.browserFileCount]) {
|
||||||
|
memcpy(app.browserFiles[app.browserFileCount], name, len);
|
||||||
app.browserIsDir[app.browserFileCount] = true;
|
app.browserIsDir[app.browserFileCount] = true;
|
||||||
app.browserFileCount++;
|
app.browserFileCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for (int i = 0; i < files.count; i++) {
|
for (int i = 0; i < files.count; i++) {
|
||||||
const char* name = GetFileName(files.paths[i]);
|
const char* name = GetFileName(files.paths[i]);
|
||||||
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) continue;
|
||||||
@@ -780,13 +795,17 @@ static void ScanDirectory(const char* path)
|
|||||||
const char* ext = GetFileExtension(files.paths[i]);
|
const char* ext = GetFileExtension(files.paths[i]);
|
||||||
if (ext && (strcmp(ext, ".wav") == 0 || strcmp(ext, ".WAV") == 0 ||
|
if (ext && (strcmp(ext, ".wav") == 0 || strcmp(ext, ".WAV") == 0 ||
|
||||||
strcmp(ext, ".Wave") == 0 || strcmp(ext, ".Wav") == 0)) {
|
strcmp(ext, ".Wave") == 0 || strcmp(ext, ".Wav") == 0)) {
|
||||||
app.browserFiles[app.browserFileCount] = strdup(name);
|
size_t len = strlen(name) + 1;
|
||||||
|
app.browserFiles[app.browserFileCount] = (char*)malloc(len);
|
||||||
|
if (app.browserFiles[app.browserFileCount]) {
|
||||||
|
memcpy(app.browserFiles[app.browserFileCount], name, len);
|
||||||
app.browserIsDir[app.browserFileCount] = false;
|
app.browserIsDir[app.browserFileCount] = false;
|
||||||
app.browserFileCount++;
|
app.browserFileCount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
UnloadDirectoryFiles(files);
|
UnloadDirectoryFiles(files);
|
||||||
app.browserScroll = 0;
|
app.browserScroll = 0;
|
||||||
app.browserSelected = -1;
|
app.browserSelected = -1;
|
||||||
|
|||||||
Reference in New Issue
Block a user