From 9998022fb57e432ee3b8b0063d03a0aa5ea2c759 Mon Sep 17 00:00:00 2001 From: Hyblocker Date: Fri, 29 May 2026 00:40:30 +0200 Subject: [PATCH] feat: begin work for linux compile --- CMakeLists.txt | 2 + src/common/platform.h | 21 +- src/common/platform_linux.cpp | 188 ++++++++++++ src/common/protocol.h | 1 + src/driver/CMakeLists.txt | 51 +++- src/driver/hooking.h | 70 ++++- src/driver/interface_hook_injector.cpp | 11 + src/driver/ipc_server.cpp | 2 +- src/driver/main.cpp | 6 +- src/driver/tracked_device_provider.cpp | 1 + src/overlay/CMakeLists.txt | 112 +++++-- src/overlay/ipc_client.cpp | 2 +- ...on.cpp => locale_identification_win64.cpp} | 4 +- src/overlay/localisation.cpp | 16 +- src/overlay/main.cpp | 12 +- src/overlay/window.h | 16 +- vendor/CMakeLists.txt | 22 +- vendor/ipc.h | 277 ++++++++++++++++-- 18 files changed, 724 insertions(+), 90 deletions(-) create mode 100644 src/common/platform_linux.cpp rename src/overlay/{locale_identification.cpp => locale_identification_win64.cpp} (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8eb1b90..a906364 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,8 @@ endfunction() # windows prefers win8.1 sdk instead of win 10 sdk for some god forsaken reason if(WIN32) set(CMAKE_SYSTEM_VERSION 10.0 CACHE STRING "Forcing Win10 SDK" FORCE) +else() + set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() project("SpaceCalibrator") diff --git a/src/common/platform.h b/src/common/platform.h index 3b8f46a..23e1418 100644 --- a/src/common/platform.h +++ b/src/common/platform.h @@ -3,7 +3,7 @@ #include #include -#ifdef _WIN32 +#if defined(_WIN32) #define OS_WINDOWS 1 #define OS_LINUX 0 #elif (defined(__gnu_linux__) || defined(__linux__)) @@ -13,6 +13,22 @@ #error "Unsupported OS" #endif +#if defined(__clang__) +#define COMPILER_MSVC 0 +#define COMPILER_GCC 0 +#define COMPILER_CLANG 1 +#elif defined(_MSC_VER) +#define COMPILER_MSVC 1 +#define COMPILER_GCC 0 +#define COMPILER_CLANG 0 +#elif defined(__GNUC__) +#define COMPILER_MSVC 0 +#define COMPILER_GCC 1 +#define COMPILER_CLANG 0 +#else +#error "Unknown compiler" +#endif + // warning guards for various compilers #if defined(__clang__) #define BEGIN_EXTERNAL_HEADERS \ @@ -55,4 +71,7 @@ namespace platform { void showMessageDialog(const std::string& title, const std::string& message); void setThreadName(const std::string& threadName); + + // utf8 stuff for imgui -> overlay -> os interop + } \ No newline at end of file diff --git a/src/common/platform_linux.cpp b/src/common/platform_linux.cpp new file mode 100644 index 0000000..44897d3 --- /dev/null +++ b/src/common/platform_linux.cpp @@ -0,0 +1,188 @@ +#include "platform.h" + +#if OS_LINUX +#include "log.h" +#include "constants.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace platform { + constexpr int INVALID_FD = -1; + + std::filesystem::path getUserConfigDir() { + // based on XDG base dir spec for XDG_DATA_HOME + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + + const char* szXdgConfigHome = getenv("XDG_DATA_HOME"); + + if (szXdgConfigHome != nullptr && szXdgConfigHome[0] != '\0') { + return std::filesystem::path(szXdgConfigHome); + } + + szXdgConfigHome = getenv("HOME"); + if (szXdgConfigHome != nullptr && szXdgConfigHome[0] != '\0') { + return std::filesystem::path(szXdgConfigHome) / ".local" / "share"; + } + + struct passwd* user_db_entry = getpwuid(getuid()); + if (user_db_entry) { + szXdgConfigHome = user_db_entry->pw_dir; + } + if (szXdgConfigHome != nullptr && szXdgConfigHome[0] != '\0') { + return std::filesystem::path(szXdgConfigHome) / ".local" / "share"; + } + + return std::filesystem::path(); + } + + std::filesystem::path getRuntimeDir() { + // based on XDG base dir spec for XDG_RUNTIME_DIR + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + + const char* szXdgRuntimeDir = getenv("XDG_RUNTIME_DIR"); + if (szXdgRuntimeDir != nullptr && szXdgRuntimeDir[0] != '\0') { + return std::filesystem::path(szXdgRuntimeDir); + } + + LOG_WARN("XDG_RUNTIME_DIR is not set, falling back to /tmp/spacecalibrator-runtime-{}", getuid()); + + std::filesystem::path fallback = std::filesystem::path("/tmp") / ("spacecalibrator-runtime-" + std::to_string(getuid())); + + std::error_code ec; + std::filesystem::create_directories(fallback, ec); + if (ec) { + LOG_ERROR("Failed to create runtime dir fallback {}: {}", fallback.string(), ec.message()); + return std::filesystem::path(); + } + + if (chmod(fallback.c_str(), 0700) != 0) { + LOG_ERROR("Failed to set permissions on runtime dir fallback {}: {}", fallback.string(), strerror(errno)); + } + + return fallback; + } + + std::filesystem::path getExeDir() { + std::error_code ec; + std::filesystem::path exePath = std::filesystem::canonical("/proc/self/exe", ec); + if (ec) { + return std::filesystem::path(); + } + return exePath; + } + + std::string getEnvVariable(const std::string& szEnvVarName) { + const char* szEnvVar = std::getenv(szEnvVarName.c_str()); + return szEnvVar ? std::string(szEnvVar) : ""; + } + + constexpr const char* s_STEAM_MUTEX_KEY = "/MUTEX__SpaceCalibrator_Steam.lock"; + int s_hSteamMutex = INVALID_FD; // flock on linux + bool s_isGitHubVersionInstalled = false; + + bool isAnotherInstanceRunning(bool& bIsRunningViaSteam) { + bIsRunningViaSteam = false; + std::string szSteamAppIdEnv = getEnvVariable("SteamAppId"); + if (spacecal::c_SPACE_CALIBRATOR_STEAM_APP_ID == szSteamAppIdEnv || + spacecal::c_STEAMVR_STEAM_APP_ID == szSteamAppIdEnv) { + // We got launched via the Steam client UI. + bIsRunningViaSteam = true; + + std::filesystem::path szRuntimeDir = getRuntimeDir(); + std::string szLockPath = (szRuntimeDir / "spacecalibrator_steam.lock").string(); + + s_hSteamMutex = open(szLockPath.c_str(), O_CREAT | O_RDWR, 0666); + if (s_hSteamMutex == INVALID_FD) { + return false; + } + + if (flock(s_hSteamMutex, LOCK_EX | LOCK_NB) == INVALID_FD) { + if (errno == EWOULDBLOCK) { + close(s_hSteamMutex); + s_hSteamMutex = INVALID_FD; + return true; + } + } + return false; + } + + return false; + } + void shutdownCurrentInstance() { + if (s_hSteamMutex != INVALID_FD) { + flock(s_hSteamMutex, LOCK_UN); + close(s_hSteamMutex); + s_hSteamMutex = INVALID_FD; + } + } + + void showMessageDialog(const std::string& title, const std::string& message) { + LOG_INFO("displaying dialog [{}]: {}", title, message); + + pid_t pid = fork(); + + if (pid == 0) { + // child process + + // redirect stderr to /dev/null to keep the console clean if tools are missing + int nullFd = open("/dev/null", O_WRONLY); + dup2(nullFd, STDERR_FILENO); + close(nullFd); + + // try invoking cli tools which provide a gui + // order of choice -> KDE -> GNOME -> notification + char* kdialogArgs[] = { + (char*)"kdialog", + (char*)"--title", (char*)title.c_str(), + (char*)"--error", (char*)message.c_str(), + nullptr + }; + execvp(kdialogArgs[0], kdialogArgs); + + char* zenityArgs[] = { + (char*)"zenity", + (char*)"--error", + (char*)"--title", (char*)title.c_str(), + (char*)"--text", (char*)message.c_str(), + (char*)"--width=300", + nullptr + }; + execvp(zenityArgs[0], zenityArgs); + + char* notifySendArgs[] = { + (char*)"notify-send", + (char*)title.c_str(), + (char*)message.c_str(), + nullptr + }; + execvp(notifySendArgs[0], notifySendArgs); + + // terminate if we failed to switch to any of the above tools + _exit(1); + } + else if (pid > 0) { + // parent process - wait for the user to dismiss the dialog + int status = 0; + waitpid(pid, &status, 0); + } else { + // fork failed + LOG_CRITICAL("Failed to fork process for message dialog."); + } + } + + void setThreadName(const std::string& threadName) { + // thread name has max 16 chars + // https://man7.org/linux/man-pages/man3/pthread_setname_np.3.html + std::string truncatedName = threadName.substr(0, 15); + pthread_setname_np(pthread_self(), truncatedName.c_str()); + } +} +#endif \ No newline at end of file diff --git a/src/common/protocol.h b/src/common/protocol.h index d65e514..411b85e 100644 --- a/src/common/protocol.h +++ b/src/common/protocol.h @@ -1,5 +1,6 @@ #pragma once +#include #include #if !defined(_OPENVR_API) diff --git a/src/driver/CMakeLists.txt b/src/driver/CMakeLists.txt index 56ad056..6832287 100644 --- a/src/driver/CMakeLists.txt +++ b/src/driver/CMakeLists.txt @@ -13,10 +13,12 @@ set(TARGET_NAME 01spacecalibrator) # This is the name that the binary will be called (driver_01spacecalibrator) set(DRIVER_NAME "driver_${TARGET_NAME}") -foreach(config_type RELEASE RELWITHDEBINFO MINSIZEREL) - set(CMAKE_CXX_FLAGS_${config_type} "${CMAKE_CXX_FLAGS_${config_type}} /Ob2 /Zc:inline") - set(CMAKE_SHARED_LINKER_FLAGS_${config_type} "${CMAKE_SHARED_LINKER_FLAGS_${config_type}} /LTCG /OPT:REF /OPT:ICF") -endforeach() +if (WIN32) + foreach(config_type RELEASE RELWITHDEBINFO MINSIZEREL) + set(CMAKE_CXX_FLAGS_${config_type} "${CMAKE_CXX_FLAGS_${config_type}} /Ob2 /Zc:inline") + set(CMAKE_SHARED_LINKER_FLAGS_${config_type} "${CMAKE_SHARED_LINKER_FLAGS_${config_type}} /LTCG /OPT:REF /OPT:ICF") + endforeach() +endif() function(GroupSourcesByFolder target) set(SOURCE_GROUP_DELIMITER "/") @@ -59,7 +61,14 @@ message(STATUS "Compilation set for ${PLATFORM}bits architectures.") # OpenVR compatibility checking if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") add_definitions(-DLINUX -DPOSIX) - set(ARCH_TARGET linux64) + + if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") + set(ARCH_TARGET linux_arm64) + add_definitions(-DLINUXARM64) + else() + set(ARCH_TARGET linux64) + add_definitions(-DLINUX64) + endif() if (${PLATFORM} MATCHES 32) message(WARNING "OpenVR x86 binaries not provided on GNU/Linux.") @@ -103,6 +112,7 @@ foreach(SOURCE IN ITEMS ${SOURCES_API}) endforeach() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}/${DRIVER_NAME}/bin/${ARCH_TARGET}>) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY $<1:${CMAKE_BINARY_DIR}/${DRIVER_NAME}/bin/${ARCH_TARGET}>) add_library(SpaceCalibratorDriver SHARED ${SOURCES_API} ${SOURCES_HEADERS}) GroupSourcesByFolder(SpaceCalibratorDriver) @@ -114,7 +124,6 @@ target_include_directories(SpaceCalibratorDriver PRIVATE ${OPENVR_LIB_DIR} PUBLIC ${CMAKE_SOURCE_DIR}/vendor PUBLIC ${CMAKE_SOURCE_DIR}/vendor/openvr/headers - PUBLIC ${CMAKE_SOURCE_DIR}/vendor/minhook/include PUBLIC ${CMAKE_SOURCE_DIR}/vendor/quill/include PUBLIC ${CMAKE_SOURCE_DIR}/vendor/fmt/include PUBLIC ${CMAKE_SOURCE_DIR}/vendor/glaze/include @@ -123,26 +132,40 @@ target_include_directories(SpaceCalibratorDriver target_link_libraries(SpaceCalibratorDriver PRIVATE SpaceCalibratorCommon PRIVATE ${OPENVR_LIBRARIES} - PRIVATE minhook PRIVATE fmt PRIVATE quill - PRIVATE kernel32 user32 gdi32 winspool comdlg32 advapi32 shell32 ole32 oleaut32 uuid odbc32 odbccp32 ) target_compile_definitions(SpaceCalibratorDriver PRIVATE $<$:_DEBUG> PRIVATE $<$:NDEBUG> - PRIVATE _WINDOWS - PRIVATE _USRDLL - PRIVATE NOMINMAX - PRIVATE UNICODE PRIVATE EIGEN_HAS_STD_RESULT_OF=0 - PRIVATE _CRT_SECURE_NO_WARNINGS PRIVATE IS_DRIVER ) + +if (WIN32) + target_include_directories(SpaceCalibratorDriver + PUBLIC ${CMAKE_SOURCE_DIR}/vendor/minhook/include) + + target_link_libraries(SpaceCalibratorDriver + PRIVATE minhook + PRIVATE kernel32 user32 gdi32 winspool comdlg32 advapi32 shell32 ole32 oleaut32 uuid odbc32 odbccp32 + ) + + target_compile_definitions(SpaceCalibratorDriver + PRIVATE _WINDOWS + PRIVATE _USRDLL + PRIVATE NOMINMAX + PRIVATE UNICODE + PRIVATE _CRT_SECURE_NO_WARNINGS + ) +endif() + set_target_properties(SpaceCalibratorDriver PROPERTIES OUTPUT_NAME ${DRIVER_NAME}) -set_target_properties(SpaceCalibratorOverlay PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS") +if (WIN32) + set_target_properties(SpaceCalibratorOverlay PROPERTIES LINK_FLAGS "/SUBSYSTEM:WINDOWS") +endif() # Copy driver assets to output folder add_custom_command( diff --git a/src/driver/hooking.h b/src/driver/hooking.h index 30b097b..869383c 100644 --- a/src/driver/hooking.h +++ b/src/driver/hooking.h @@ -1,10 +1,19 @@ #pragma once -#include #include #include #include "log.h" #include "platform.h" +#if OS_WINDOWS +#include +#elif OS_LINUX +#include +#include +#include + +#define ALIGN_TO_PAGE(addr, pageSize) \ + ((void*)((uintptr_t)(addr) & ~(uintptr_t)((pageSize) - 1))) +#endif namespace hooking { class IHook @@ -38,6 +47,7 @@ namespace hooking { // To access the vtable, we simply dereference the object. void** vtable = *((void***)object); +#if OS_WINDOWS // The vtable itself is an array of pointers to member functions, // in the order they were declared in. targetFunc = vtable[vtableOffset]; @@ -45,34 +55,86 @@ namespace hooking { auto err = MH_CreateHook(targetFunc, detourFunction, (LPVOID*)&originalFunc); if (err != MH_OK) { - LOG_HOOKING_ERROR("Failed to create hook for {}, error: {}", name.c_str(), MH_StatusToString(err)); + LOG_HOOKING_ERROR("Failed to create hook for {}, error: {}", name, MH_StatusToString(err)); return false; } err = MH_EnableHook(targetFunc); if (err != MH_OK) { - LOG_HOOKING_ERROR("Failed to enable hook for {}, error: {}", name.c_str(), MH_StatusToString(err)); + LOG_HOOKING_ERROR("Failed to enable hook for {}, error: {}", name, MH_StatusToString(err)); MH_RemoveHook(targetFunc); return false; } +#elif OS_LINUX + targetFunc = (void**)&vtable[vtableOffset]; + originalFunc = (FuncType)vtable[vtableOffset]; - LOG_HOOKING_INFO("Enabled hook for {}", name.c_str()); + long pageSize = sysconf(_SC_PAGESIZE); + void* pageStart = ALIGN_TO_PAGE(targetFunc, pageSize); + void* pageEnd = ALIGN_TO_PAGE((uintptr_t)targetFunc + sizeof(void*) - 1, pageSize); + size_t length = (uintptr_t)pageEnd - (uintptr_t)pageStart + pageSize; + + if (mprotect(pageStart, length, PROT_READ | PROT_WRITE | PROT_EXEC) != 0) { + LOG_HOOKING_ERROR("Failed to make vtable writable for {}: {}", name, strerror(errno)); + return false; + } + + *targetFunc = detourFunction; + +#if COMPILER_GCC || COMPILER_CLANG + // https://blog.pathogenstudios.com/rolling-your-own-breakpoints-without-0xcc/ + // https://github.com/ridpath/gamehacking-cheatsheet#apple-silicon-rosetta-2-game-reversing + // flush cache -> required for aarch64 + // emits no-op on amd64 (done automatically by hardware on amd64 so no need) + __builtin___clear_cache( + reinterpret_cast(targetFunc), + reinterpret_cast(targetFunc) + sizeof(void*) + ); +#endif // COMPILER_GCC || COMPILER_CLANG + + if (mprotect(pageStart, length, PROT_READ | PROT_EXEC) != 0) { + LOG_HOOKING_ERROR("Failed to restore vtable protection for {}: {}", name, strerror(errno)); + } +#endif // OS_LINUX + + LOG_HOOKING_INFO("Enabled hook for {}", name); enabled = true; return true; } void Destroy() { +#if OS_WINDOWS if (enabled) { MH_RemoveHook(targetFunc); enabled = false; } +#elif OS_LINUX + if (enabled && targetFunc) + { + long pageSize = sysconf(_SC_PAGESIZE); + void* pageStart = ALIGN_TO_PAGE(targetFunc, pageSize); + void* pageEnd = ALIGN_TO_PAGE((uintptr_t)targetFunc + sizeof(void*) - 1, pageSize); + size_t length = (uintptr_t)pageEnd - (uintptr_t)pageStart + pageSize; + + if (mprotect(pageStart, length, PROT_READ | PROT_WRITE | PROT_EXEC) == 0) { + *targetFunc = (void*)originalFunc; + mprotect(pageStart, length, PROT_READ | PROT_EXEC); + } + enabled = false; + } +#endif // OS_LINUX } private: bool enabled = false; +#if OS_WINDOWS void* targetFunc = nullptr; +#elif OS_LINUX + void** targetFunc = nullptr; +#undef ALIGN_TO_PAGE +#endif }; } \ No newline at end of file diff --git a/src/driver/interface_hook_injector.cpp b/src/driver/interface_hook_injector.cpp index 2712228..24516e4 100644 --- a/src/driver/interface_hook_injector.cpp +++ b/src/driver/interface_hook_injector.cpp @@ -64,6 +64,7 @@ namespace hooking { void InjectHooks(spacecal::ServerTrackedDeviceProvider* driver, vr::IVRDriverContext* pDriverContext) { g_mainDriver = driver; +#if OS_WINDOWS MH_STATUS err = MH_Initialize(); if (err == MH_OK) { GetGenericInterfaceHook.CreateHookInObjectVTable(pDriverContext, 0, reinterpret_cast(&DetourGetGenericInterface)); @@ -74,11 +75,21 @@ namespace hooking { } else { LOG_HOOKING_ERROR("MH_Initialize error: {}", MH_StatusToString(err)); } +#elif OS_LINUX + if (GetGenericInterfaceHook.CreateHookInObjectVTable(pDriverContext, 0, reinterpret_cast(&DetourGetGenericInterface))) { + IHook::Register(&GetGenericInterfaceHook); + LOG_HOOKING_INFO("Space Calibrator hooked into OpenVR PoseUpdate successfully"); + } else { + LOG_HOOKING_ERROR("Failed to hook into OpenVR PoseUpdate."); + } +#endif } void DisableHooks() { IHook::DestroyAll(); +#if OS_WINDOWS MH_Uninitialize(); +#endif LOG_HOOKING_INFO("Removed all hooks successfully"); } } \ No newline at end of file diff --git a/src/driver/ipc_server.cpp b/src/driver/ipc_server.cpp index 060c136..d0a4cec 100644 --- a/src/driver/ipc_server.cpp +++ b/src/driver/ipc_server.cpp @@ -120,7 +120,7 @@ namespace ipc { .szSharedMemoryName = protocol::k_szIpcIdentifier, .dwSharedBufferSizeBytes = protocol::k_unSharedMemoryElementSize, .aFunctions = m_funcs, - .dwFunctionCount = _countof(m_funcs), + .dwFunctionCount = IPC_COUNT_OF(m_funcs), .aOperations = nullptr, .dwOperationCount = 0, .userData = this, diff --git a/src/driver/main.cpp b/src/driver/main.cpp index be2cc28..29c6eb1 100644 --- a/src/driver/main.cpp +++ b/src/driver/main.cpp @@ -1,10 +1,12 @@ #include +#include #include "tracked_device_provider.h" +#include "platform.h" -#if defined( _WIN32 ) +#if OS_WINDOWS #define SPACECALIBRATORDRIVER_EXPORT extern "C" __declspec( dllexport ) #define SPACECALIBRATORDRIVER_IMPORT extern "C" __declspec( dllimport ) -#elif defined( __GNUC__ ) || defined( COMPILER_GCC ) || defined( __APPLE__ ) +#elif COMPILER_GCC || OS_LINUX #define SPACECALIBRATORDRIVER_EXPORT extern "C" __attribute__( ( visibility( "default" ) ) ) #define SPACECALIBRATORDRIVER_IMPORT extern "C" #else diff --git a/src/driver/tracked_device_provider.cpp b/src/driver/tracked_device_provider.cpp index 4984143..06c10c2 100644 --- a/src/driver/tracked_device_provider.cpp +++ b/src/driver/tracked_device_provider.cpp @@ -6,6 +6,7 @@ #include "interface_hook_injector.h" #include "vrmath.h" #include "virtual_desktop.h" +#include #include #include diff --git a/src/overlay/CMakeLists.txt b/src/overlay/CMakeLists.txt index 2f02445..e8bd367 100644 --- a/src/overlay/CMakeLists.txt +++ b/src/overlay/CMakeLists.txt @@ -3,10 +3,12 @@ cmake_minimum_required (VERSION 3.8) project(SpaceCalibratorOverlay CXX) message("SpaceCalibrator - Overlay") -foreach(config_type RELEASE RELWITHDEBINFO MINSIZEREL) - set(CMAKE_CXX_FLAGS_${config_type} "${CMAKE_CXX_FLAGS_${config_type}} /Ob2 /Zc:inline") - set(CMAKE_SHARED_LINKER_FLAGS_${config_type} "${CMAKE_SHARED_LINKER_FLAGS_${config_type}} /LTCG /OPT:REF /OPT:ICF") -endforeach() +if (WIN32) + foreach(config_type RELEASE RELWITHDEBINFO MINSIZEREL) + set(CMAKE_CXX_FLAGS_${config_type} "${CMAKE_CXX_FLAGS_${config_type}} /Ob2 /Zc:inline") + set(CMAKE_SHARED_LINKER_FLAGS_${config_type} "${CMAKE_SHARED_LINKER_FLAGS_${config_type}} /LTCG /OPT:REF /OPT:ICF") + endforeach() +endif() function(GroupSourcesByFolder target) set(SOURCE_GROUP_DELIMITER "/") @@ -33,6 +35,60 @@ function(GroupSourcesByFolder target) endif() endfunction() +set(OPENVR_LIB_DIR ${CMAKE_SOURCE_DIR}/vendor/openvr) + +# If not set, determine the platform architecture +if (NOT PLATFORM) + if (CMAKE_SIZEOF_VOID_P MATCHES 8) + set(PLATFORM 64) + else () + set(PLATFORM 32) + endif () +endif () +message(STATUS "Compilation set for ${PLATFORM}bits architectures.") + +# OpenVR compatibility checking +if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") + add_definitions(-DLINUX -DPOSIX) + + if (CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64") + set(ARCH_TARGET linux_arm64) + add_definitions(-DLINUXARM64) + else() + set(ARCH_TARGET linux64) + add_definitions(-DLINUX64) + endif() + + if (${PLATFORM} MATCHES 32) + message(WARNING "OpenVR x86 binaries not provided on GNU/Linux.") + endif () +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + set(CMAKE_MACOSX_RPATH 0) + add_definitions(-DOSX -DPOSIX) + set(ARCH_TARGET osx32) + +elseif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") + add_definitions(-D_WIN32) + set(ARCH_TARGET win${PLATFORM}) + + # Binaries path for thirdparties are not generics so we try to guess their suffixes. + set(WINDOWS_PATH_SUFFIXES win${PLATFORM} Win${PLATFORM} x${PLATFORM}) +endif () + +find_library(OPENVR_LIBRARIES + NAMES + openvr_api + PATHS + ${OPENVR_LIB_DIR}/bin + ${OPENVR_LIB_DIR}/lib + PATH_SUFFIXES + osx${PLATFORM} + linux${PLATFORM} + win${PLATFORM} + NO_DEFAULT_PATH + NO_CMAKE_FIND_ROOT_PATH + ) + set(CMAKE_CXX_STANDARD 23) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -83,28 +139,33 @@ target_include_directories(SpaceCalibratorOverlay target_link_libraries(SpaceCalibratorOverlay PRIVATE SpaceCalibratorCommon - PRIVATE opengl32.lib - PRIVATE dwmapi.lib PRIVATE imgui PRIVATE glad PRIVATE glfw PRIVATE fmt PRIVATE quill - PRIVATE ${CMAKE_SOURCE_DIR}/vendor/openvr/lib/win64/openvr_api.lib) + PRIVATE ${OPENVR_LIBRARIES}) target_compile_definitions(SpaceCalibratorOverlay - PRIVATE NOMINMAX - PRIVATE UNICODE - PRIVATE _CRT_SECURE_NO_WARNINGS PRIVATE IMGUI_DEFINE_MATH_OPERATORS PRIVATE IS_OVERLAY ) +if (WIN32) + target_link_libraries(SpaceCalibratorOverlay + PRIVATE opengl32.lib + PRIVATE dwmapi.lib) + + target_compile_definitions(SpaceCalibratorOverlay + PRIVATE NOMINMAX + PRIVATE UNICODE + PRIVATE _CRT_SECURE_NO_WARNINGS + ) +endif() + # Object files are too big with spacecal sooooo if (MSVC) target_compile_options(SpaceCalibratorOverlay PRIVATE /bigobj) -else () - target_compile_options(SpaceCalibratorOverlay PRIVATE -Wa,-mbig-obj) endif () set(OVERLAY_BIN_DIR "$") @@ -150,12 +211,21 @@ add_custom_command( ) # Copy openvr_api.dll -add_custom_command( - TARGET SpaceCalibratorOverlay POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_SOURCE_DIR}/vendor/openvr/bin/win64/openvr_api.dll - ${OVERLAY_BIN_DIR}/openvr_api.dll -) +if (WIN32) + add_custom_command( + TARGET SpaceCalibratorOverlay POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_SOURCE_DIR}/vendor/openvr/bin/${ARCH_TARGET}/openvr_api.dll + ${OVERLAY_BIN_DIR}/openvr_api.dll + ) +else() + add_custom_command( + TARGET SpaceCalibratorOverlay POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_SOURCE_DIR}/vendor/openvr/bin/${ARCH_TARGET}/libopenvr_api.so + ${OVERLAY_BIN_DIR}/libopenvr_api.so + ) +endif() # Copy assets to output folder add_custom_command( @@ -168,6 +238,8 @@ add_custom_command( ) # Set as startup project -set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT SpaceCalibratorOverlay) -set_property(TARGET SpaceCalibratorOverlay PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${OVERLAY_BIN_DIR}") +if (WIN32) + set_property(DIRECTORY ${CMAKE_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT SpaceCalibratorOverlay) + set_property(TARGET SpaceCalibratorOverlay PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${OVERLAY_BIN_DIR}") +endif() set_target_properties(SpaceCalibratorOverlay PROPERTIES OUTPUT_NAME "SpaceCalibrator") \ No newline at end of file diff --git a/src/overlay/ipc_client.cpp b/src/overlay/ipc_client.cpp index cbb007c..42ddf95 100644 --- a/src/overlay/ipc_client.cpp +++ b/src/overlay/ipc_client.cpp @@ -32,7 +32,7 @@ namespace ipc { .szSharedMemoryName = protocol::k_szIpcIdentifier, .dwSharedBufferSizeBytes = protocol::k_unSharedMemoryElementSize, .aFunctions = m_funcs, - .dwFunctionCount = _countof(m_funcs), + .dwFunctionCount = IPC_COUNT_OF(m_funcs), .aOperations = nullptr, .dwOperationCount = 0, .userData = this, diff --git a/src/overlay/locale_identification.cpp b/src/overlay/locale_identification_win64.cpp similarity index 99% rename from src/overlay/locale_identification.cpp rename to src/overlay/locale_identification_win64.cpp index 12a4af0..c03a764 100644 --- a/src/overlay/locale_identification.cpp +++ b/src/overlay/locale_identification_win64.cpp @@ -1,5 +1,6 @@ #include "localisation.h" +#if OS_WINDOWS #include "log.h" #include @@ -384,4 +385,5 @@ namespace spacecal { return estimatedLocale; } -} // namespace localisation \ No newline at end of file +} // namespace localisation +#endif // OS_WINDOWS \ No newline at end of file diff --git a/src/overlay/localisation.cpp b/src/overlay/localisation.cpp index 06ddb08..fc75c2d 100644 --- a/src/overlay/localisation.cpp +++ b/src/overlay/localisation.cpp @@ -4,6 +4,8 @@ #include "platform.h" #include "util.h" +#include + BEGIN_EXTERNAL_HEADERS #include #include @@ -116,9 +118,8 @@ namespace spacecal { auto rootDir = util::getSpaceCalibratorLangsDir(); std::string langPath = (rootDir / (getLocaleAsRegionString(locale) + ".json")).string(); - FILE* localeFile = nullptr; - errno_t fileErr = fopen_s(&localeFile, langPath.c_str(), "rb"); - switch (fileErr) { + FILE* localeFile = fopen(langPath.c_str(), "rb"); + switch (errno) { case 0: // OK break; case ENOENT: @@ -135,7 +136,7 @@ namespace spacecal { LOG_WARNING("The locale file \"{0}\" could not be loaded as it was too large.", langPath); return false; default: - LOG_WARNING("The locale file \"{0}\" could not be loaded due to an unknown error ({1}).", langPath, fileErr); + LOG_WARNING("The locale file \"{0}\" could not be loaded due to an unknown error ({1}).", langPath, errno); return false; } if (localeFile) { @@ -202,9 +203,8 @@ namespace spacecal { // fallback to the loaded locale string m_nativeLanguageNames[locale] = getString(expectedKey); - FILE* file = nullptr; - errno_t fileErr = fopen_s(&file, langPath.c_str(), "rb"); - switch (fileErr) { + FILE* file = fopen(langPath.c_str(), "rb"); + switch (errno) { case 0: // OK break; case ENOENT: @@ -221,7 +221,7 @@ namespace spacecal { LOG_WARNING("The locale file \"{0}\" could not be loaded as it was too large.", langPath); return; default: - LOG_WARNING("The locale file \"{0}\" could not be loaded due to an unknown error ({1}).", langPath, fileErr); + LOG_WARNING("The locale file \"{0}\" could not be loaded due to an unknown error ({1}).", langPath, errno); return; } diff --git a/src/overlay/main.cpp b/src/overlay/main.cpp index 675e10e..3b442a4 100644 --- a/src/overlay/main.cpp +++ b/src/overlay/main.cpp @@ -1,5 +1,4 @@ #include -#include #include "util.h" #include "window.h" #include "platform.h" @@ -8,13 +7,24 @@ #include "localisation.h" #include "vr_core.h" #include "log.h" +#if OS_WINDOWS +#include +#endif +#if OS_WINDOWS // http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf extern "C" __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; // http://developer.amd.com/community/blog/2015/10/02/amd-enduro-system-for-developers/ extern "C" __declspec(dllexport) DWORD AmdPowerXpressRequestHighPerformance = 0x00000001; +#endif +#if OS_WINDOWS int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) +#elif OS_LINUX +int main(int argc, char* argv[]) +#else +#error "Unsupported platform!" +#endif { // Space Calibrator has to be single instance to work well with Steam bool bIsRunningViaSteam = false; diff --git a/src/overlay/window.h b/src/overlay/window.h index 68b5164..96282f2 100644 --- a/src/overlay/window.h +++ b/src/overlay/window.h @@ -1,12 +1,24 @@ #pragma once +#include "platform.h" +#include "ipc_client.h" +#include "calibration.h" + #include +#if OS_WINDOWS #define GLFW_EXPOSE_NATIVE_WIN32 +#elif OS_LINUX +#define GLFW_EXPOSE_NATIVE_WAYLAND +#define GLFW_EXPOSE_NATIVE_X11 +#endif #include #include -#include "ipc_client.h" -#include "calibration.h" +#if OS_LINUX + #ifdef None + #undef None + #endif +#endif namespace spacecal { // handles window creation, setting the icon, and initialising a renderer for UI, also handles connecting it to SteamVR as an overlay diff --git a/vendor/CMakeLists.txt b/vendor/CMakeLists.txt index 51f6186..65cd0fc 100644 --- a/vendor/CMakeLists.txt +++ b/vendor/CMakeLists.txt @@ -5,8 +5,10 @@ project("vendor") # Includes all 3rd party libraries here # Minhook library -message("Adding minhook...") -add_subdirectory ("minhook") +if (WIN32) + message("Adding minhook...") + add_subdirectory ("minhook") +endif() # glad message("Adding glad...") @@ -53,11 +55,13 @@ message ("Adding glaze...") add_subdirectory(glaze) # Assign solution dirs -set_property(TARGET glfw PROPERTY FOLDER "vendor/glfw") -set_property(TARGET update_mappings PROPERTY FOLDER "vendor/glfw") +if (WIN32) + set_property(TARGET glfw PROPERTY FOLDER "vendor/glfw") + set_property(TARGET update_mappings PROPERTY FOLDER "vendor/glfw") -set_property(TARGET minhook PROPERTY FOLDER "vendor") -set_property(TARGET imgui PROPERTY FOLDER "vendor") -set_property(TARGET fmt PROPERTY FOLDER "vendor") -set_property(TARGET quill PROPERTY FOLDER "vendor") -set_property(TARGET glad PROPERTY FOLDER "vendor") \ No newline at end of file + set_property(TARGET minhook PROPERTY FOLDER "vendor") + set_property(TARGET imgui PROPERTY FOLDER "vendor") + set_property(TARGET fmt PROPERTY FOLDER "vendor") + set_property(TARGET quill PROPERTY FOLDER "vendor") + set_property(TARGET glad PROPERTY FOLDER "vendor") +endif() \ No newline at end of file diff --git a/vendor/ipc.h b/vendor/ipc.h index f17b80c..d8379ed 100644 --- a/vendor/ipc.h +++ b/vendor/ipc.h @@ -43,6 +43,8 @@ // o-----------------o // +#define IPC_COUNT_OF(arr) (sizeof(arr) / sizeof((arr)[0])) + typedef void* IpcHandle_t; static const IpcHandle_t k_hInvalidIpcHandle = ((IpcHandle_t)(-1)); typedef uint64_t IpcCommandType_t; @@ -89,7 +91,20 @@ bool ipc_client_read_shared_memory(IpcHandle_t hIpcClient, IpcOperation_t ipcOpe #ifdef IPC_IMPLEMENTATION +#if defined(_WIN32) +#define IPC_OS_WIN32 1 +#else +#define IPC_OS_WIN32 0 +#endif + +#if defined(__gnu_linux__) || defined(__linux__) +#define IPC_OS_LINUX 1 +#else +#define IPC_OS_LINUX 0 +#endif + #include +#include #include #include #include @@ -100,7 +115,7 @@ bool ipc_client_read_shared_memory(IpcHandle_t hIpcClient, IpcOperation_t ipcOpe #define IPC_FUNCTION_ARGS_TOTAL_SIZE (1024*16) #endif // IPC_FUNCTION_ARGS_TOTAL_SIZE -#if _WIN32 +#if IPC_OS_WIN32 #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif // WIN32_LEAN_AND_MEAN @@ -117,6 +132,11 @@ bool ipc_client_read_shared_memory(IpcHandle_t hIpcClient, IpcOperation_t ipcOpe #define NOMINMAX #endif // NOMINMAX #include +#elif IPC_OS_LINUX +#include +#include +#include +#include #endif struct __internal__IpcFunction_t { @@ -139,10 +159,14 @@ struct __internal__IpcHandle_t { void* userData = nullptr; }; +#if IPC_OS_LINUX +#define IPC_S_RW_ALL (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) +#endif + void __internal_ipc_server_poll_requests(IpcHandle_t hIpcServer) { __internal__IpcHandle_t* handleInternal = (__internal__IpcHandle_t*)hIpcServer; -#if _WIN32 +#if IPC_OS_WIN32 while (handleInternal->is_running) { DWORD result = WaitForSingleObject((HANDLE)handleInternal->hNativeEvent, 100); @@ -164,6 +188,41 @@ void __internal_ipc_server_poll_requests(IpcHandle_t hIpcServer) { ReleaseMutex((HANDLE)handleInternal->hNativeMutex); } } +#elif IPC_OS_LINUX + sem_t* semEvt = (sem_t*)handleInternal->hNativeEvent; + sem_t* semMtx = (sem_t*)handleInternal->hNativeMutex; + + while (handleInternal->is_running) { + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) continue; + ts.tv_nsec += 100000000; // 100ms + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec += 1; + ts.tv_nsec -= 1000000000; + } + + int res = sem_timedwait(semEvt, &ts); + if (res == 0) { + if (sem_wait(semMtx) == 0) { + IpcCommandType_t* cmdType = (IpcCommandType_t*)handleInternal->pSharedMemory; + void* pArgs = (void*)((char*)handleInternal->pSharedMemory + sizeof(IpcCommandType_t)); + + for (const auto& func : handleInternal->registed_functions) { + if (func.unCmdType == *cmdType) { + if (func.pfnCallback) { + func.pfnCallback(*cmdType, hIpcServer, pArgs, handleInternal->userData); + } + break; + } + } + + sem_post(semMtx); + } + } + else if (errno != ETIMEDOUT && errno != EINTR) { + handleInternal->is_running = false; + } + } #else #error "Unsupported platform" #endif @@ -184,7 +243,7 @@ IpcHandle_t ipc_server_init(IpcCreateArguments_t args) { handleInternal->szSharedMemoryName = args.szSharedMemoryName; size_t totalSharedMemSize = IPC_FUNCTION_ARGS_TOTAL_SIZE + args.dwSharedBufferSizeBytes; -#if _WIN32 +#if IPC_OS_WIN32 handleInternal->hSharedMemory = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)totalSharedMemSize, args.szSharedMemoryName); if (handleInternal->hSharedMemory == NULL) { free(handleInternal); @@ -207,6 +266,39 @@ IpcHandle_t ipc_server_init(IpcCreateArguments_t args) { ipc_server_shutdown(&hIpcHandle); return k_hInvalidIpcHandle; } +#elif IPC_OS_LINUX + // shared mem needs a leading / on linux + std::string szSharedMemoryPath = (args.szSharedMemoryName[0] == '/') ? args.szSharedMemoryName : "/" + std::string(args.szSharedMemoryName); + + int hSharedMemFd = shm_open(szSharedMemoryPath.c_str(), O_CREAT | O_RDWR, IPC_S_RW_ALL); + if (hSharedMemFd == -1) { + free(handleInternal); + return k_hInvalidIpcHandle; + } + + handleInternal->hSharedMemory = (IpcNativeHandle_t)(intptr_t)hSharedMemFd; + ftruncate(hSharedMemFd, totalSharedMemSize); + + handleInternal->pSharedMemory = mmap(NULL, totalSharedMemSize, PROT_READ | PROT_WRITE, MAP_SHARED, hSharedMemFd, 0); + if (handleInternal->pSharedMemory == MAP_FAILED) { + close(hSharedMemFd); + free(handleInternal); + return k_hInvalidIpcHandle; + } + + std::string mutexName = szSharedMemoryPath + "_FUNC_MTX"; + std::string eventName = szSharedMemoryPath + "_FUNC_EVT"; + + handleInternal->hNativeMutex = (IpcNativeHandle_t)sem_open(mutexName.c_str(), O_CREAT, IPC_S_RW_ALL, 1); + handleInternal->hNativeEvent = (IpcNativeHandle_t)sem_open(eventName.c_str(), O_CREAT, IPC_S_RW_ALL, 0); + if (handleInternal->hNativeMutex == SEM_FAILED || handleInternal->hNativeEvent == SEM_FAILED) { + IpcHandle_t hIpcHandle = ((IpcHandle_t)handleInternal); + ipc_server_shutdown(&hIpcHandle); + return k_hInvalidIpcHandle; + } +#else +#error "Unsupported platform" +#endif for (size_t i = 0; i < args.dwFunctionCount; ++i) { const IpcFunction_t& reg = args.aFunctions[i]; @@ -219,9 +311,6 @@ IpcHandle_t ipc_server_init(IpcCreateArguments_t args) { handleInternal->is_running = true; handleInternal->poll_thread = std::thread(__internal_ipc_server_poll_requests, (IpcHandle_t)handleInternal); -#else -#error "Unsupported platform" -#endif return (IpcHandle_t)handleInternal; } @@ -239,7 +328,7 @@ IpcHandle_t ipc_client_init(IpcCreateArguments_t args) { handleInternal->szSharedMemoryName = args.szSharedMemoryName; -#if _WIN32 +#if IPC_OS_WIN32 handleInternal->hSharedMemory = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, args.szSharedMemoryName); if (handleInternal->hSharedMemory == NULL) { free(handleInternal); @@ -262,6 +351,32 @@ IpcHandle_t ipc_client_init(IpcCreateArguments_t args) { ipc_client_shutdown(&handleFinal); return k_hInvalidIpcHandle; } +#elif IPC_OS_LINUX + std::string szSharedMemoryPath = (args.szSharedMemoryName[0] == '/') ? args.szSharedMemoryName : "/" + std::string(args.szSharedMemoryName); + + int hSharedMemFd = shm_open(szSharedMemoryPath.c_str(), O_RDWR, IPC_S_RW_ALL); + if (hSharedMemFd == -1) { + free(handleInternal); + return k_hInvalidIpcHandle; + } + + handleInternal->hSharedMemory = (IpcNativeHandle_t)(intptr_t)hSharedMemFd; + struct stat st = {}; + fstat(hSharedMemFd, &st); + handleInternal->pSharedMemory = mmap(NULL, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, hSharedMemFd, 0); + + std::string mutexName = szSharedMemoryPath + "_FUNC_MTX"; + std::string eventName = szSharedMemoryPath + "_FUNC_EVT"; + handleInternal->hNativeMutex = (IpcNativeHandle_t)sem_open(mutexName.c_str(), 1); + handleInternal->hNativeEvent = (IpcNativeHandle_t)sem_open(eventName.c_str(), 0); + if (handleInternal->hNativeMutex == SEM_FAILED || handleInternal->hNativeEvent == SEM_FAILED) { + IpcHandle_t hIpcHandle = ((IpcHandle_t)handleInternal); + ipc_client_shutdown(&hIpcHandle); + return k_hInvalidIpcHandle; + } +#else +#error "Unsupported platform" +#endif for (size_t i = 0; i < args.dwFunctionCount; ++i) { const IpcFunction_t& reg = args.aFunctions[i]; @@ -270,9 +385,6 @@ IpcHandle_t ipc_client_init(IpcCreateArguments_t args) { }; handleInternal->registed_functions.push_back(func); } -#else -#error "Unsupported platform" -#endif return (IpcHandle_t)handleInternal; } @@ -281,11 +393,11 @@ bool ipc_server_shutdown(IpcHandle_t* hIpcServer) { if (hIpcServer && *hIpcServer != k_hInvalidIpcHandle) { __internal__IpcHandle_t* handleInternal = (__internal__IpcHandle_t*)*hIpcServer; -#if _WIN32 handleInternal->is_running = false; if (handleInternal->poll_thread.joinable()) { handleInternal->poll_thread.join(); } +#if IPC_OS_WIN32 for (size_t i = 0; i < handleInternal->registed_operations.size(); ++i) { if (handleInternal->registed_operations[i].hNativeEvent) @@ -303,6 +415,40 @@ bool ipc_server_shutdown(IpcHandle_t* hIpcServer) { UnmapViewOfFile(handleInternal->pSharedMemory); if (handleInternal->hSharedMemory) CloseHandle(handleInternal->hSharedMemory); +#elif IPC_OS_LINUX + std::string szSharedMemoryPath = (handleInternal->szSharedMemoryName[0] == '/') ? handleInternal->szSharedMemoryName : "/" + std::string(handleInternal->szSharedMemoryName); + + if ((sem_t*)handleInternal->hNativeMutex != SEM_FAILED) { + sem_close((sem_t*)handleInternal->hNativeMutex); + sem_unlink((szSharedMemoryPath + "_FUNC_MTX").c_str()); + } + if ((sem_t*)handleInternal->hNativeEvent != SEM_FAILED) { + sem_close((sem_t*)handleInternal->hNativeEvent); + sem_unlink((szSharedMemoryPath + "_FUNC_EVT").c_str()); + } + + for (size_t i = 0; i < handleInternal->registed_operations.size(); ++i) { + IpcOperation_t& op = handleInternal->registed_operations[i]; + std::string opMutexName = szSharedMemoryPath + "_" + std::string(op.szIdentifier) + "_MTX"; + std::string opEventName = szSharedMemoryPath + "_" + std::string(op.szIdentifier) + "_EVT"; + if ((sem_t*)op.hNativeMutex != SEM_FAILED) { + sem_close((sem_t*)op.hNativeMutex); + sem_unlink(opMutexName.c_str()); + } + if ((sem_t*)op.hNativeEvent != SEM_FAILED) { + sem_close((sem_t*)op.hNativeEvent); + sem_unlink(opEventName.c_str()); + } + } + + if (handleInternal->pSharedMemory) { + struct stat st = {}; + fstat((int)(intptr_t)handleInternal->hSharedMemory, &st); + munmap(handleInternal->pSharedMemory, st.st_size); + } + if (handleInternal->hSharedMemory) + close((int)(intptr_t)handleInternal->hSharedMemory); + shm_unlink(szSharedMemoryPath.c_str()); #else #error "Unsupported platform" #endif @@ -317,7 +463,7 @@ bool ipc_client_shutdown(IpcHandle_t* hIpcClient) { if (hIpcClient && *hIpcClient != k_hInvalidIpcHandle) { __internal__IpcHandle_t* handleInternal = (__internal__IpcHandle_t*)*hIpcClient; -#if _WIN32 +#if IPC_OS_WIN32 for (size_t i = 0; i < handleInternal->registed_operations.size(); ++i) { if (handleInternal->registed_operations[i].hNativeEvent) CloseHandle((HANDLE)handleInternal->registed_operations[i].hNativeEvent); @@ -334,6 +480,29 @@ bool ipc_client_shutdown(IpcHandle_t* hIpcClient) { UnmapViewOfFile(handleInternal->pSharedMemory); if (handleInternal->hSharedMemory) CloseHandle(handleInternal->hSharedMemory); +#elif IPC_OS_LINUX + std::string szSharedMemoryPath = (handleInternal->szSharedMemoryName[0] == '/') ? handleInternal->szSharedMemoryName : "/" + std::string(handleInternal->szSharedMemoryName); + + for (size_t i = 0; i < handleInternal->registed_operations.size(); ++i) { + IpcOperation_t& op = handleInternal->registed_operations[i]; + if ((sem_t*)op.hNativeMutex != SEM_FAILED) + sem_close((sem_t*)op.hNativeMutex); + if ((sem_t*)op.hNativeEvent != SEM_FAILED) + sem_close((sem_t*)op.hNativeEvent); + } + + if ((sem_t*)handleInternal->hNativeMutex != SEM_FAILED) + sem_close((sem_t*)handleInternal->hNativeMutex); + if ((sem_t*)handleInternal->hNativeEvent != SEM_FAILED) + sem_close((sem_t*)handleInternal->hNativeEvent); + + if (handleInternal->pSharedMemory) { + struct stat st = {}; + fstat((int)(intptr_t)handleInternal->hSharedMemory, &st); + munmap(handleInternal->pSharedMemory, st.st_size); + } + if (handleInternal->hSharedMemory) + close((int)(intptr_t)handleInternal->hSharedMemory); #else #error "Unsupported platform" #endif @@ -363,14 +532,23 @@ bool ipc_client_dispatch_function(IpcHandle_t hIpcClient, IpcCommandType_t unCmd return false; } -#if _WIN32 +#if IPC_OS_WIN32 WaitForSingleObject((HANDLE)handleInternal->hNativeMutex, INFINITE); void* pSharedMem = handleInternal->pSharedMemory; *((IpcCommandType_t*)pSharedMem) = unCmdType; memcpy((char*)pSharedMem + sizeof(IpcCommandType_t), pArgs, dwArgsSize); SetEvent((HANDLE)handleInternal->hNativeEvent); - WaitForSingleObject((HANDLE)handleInternal->hNativeEvent, INFINITE); ReleaseMutex((HANDLE)handleInternal->hNativeMutex); +#elif IPC_OS_LINUX + sem_t* semMtx = (sem_t*)handleInternal->hNativeMutex; + sem_t* semEvt = (sem_t*)handleInternal->hNativeEvent; + + sem_wait(semMtx); + void* pSharedMem = handleInternal->pSharedMemory; + *((IpcCommandType_t*)pSharedMem) = unCmdType; + memcpy((char*)pSharedMem + sizeof(IpcCommandType_t), pArgs, dwArgsSize); + sem_post(semEvt); + sem_post(semMtx); #else #error "Unsupported platform" #endif @@ -384,7 +562,7 @@ bool ipc_server_register_operation(IpcHandle_t hIpcServer, IpcOperation_t* ipcOp } __internal__IpcHandle_t* handleInternal = (__internal__IpcHandle_t*)hIpcServer; -#if _WIN32 +#if IPC_OS_WIN32 std::string mutexName = std::string(handleInternal->szSharedMemoryName) + "_" + std::string(ipcOperation->szIdentifier) + "_MTX"; std::string eventName = std::string(handleInternal->szSharedMemoryName) + "_" + std::string(ipcOperation->szIdentifier) + "_EVT"; @@ -398,12 +576,31 @@ bool ipc_server_register_operation(IpcHandle_t hIpcServer, IpcOperation_t* ipcOp CloseHandle((HANDLE)ipcOperation->hNativeEvent); return false; } +#elif IPC_OS_LINUX + std::string szSharedMemoryPath = (handleInternal->szSharedMemoryName[0] == '/') ? handleInternal->szSharedMemoryName : "/" + std::string(handleInternal->szSharedMemoryName); + std::string mutexName = szSharedMemoryPath + "_" + std::string(ipcOperation->szIdentifier) + "_MTX"; + std::string eventName = szSharedMemoryPath + "_" + std::string(ipcOperation->szIdentifier) + "_EVT"; - handleInternal->registed_operations.push_back(*ipcOperation); + ipcOperation->hNativeMutex = (IpcNativeHandle_t)sem_open(mutexName.c_str(), O_CREAT, IPC_S_RW_ALL, 1); + ipcOperation->hNativeEvent = (IpcNativeHandle_t)sem_open(eventName.c_str(), O_CREAT, IPC_S_RW_ALL, 0); + + if (ipcOperation->hNativeMutex == SEM_FAILED || ipcOperation->hNativeEvent == SEM_FAILED) { + if ((sem_t*)ipcOperation->hNativeMutex != SEM_FAILED) { + sem_close((sem_t*)ipcOperation->hNativeMutex); + sem_unlink(mutexName.c_str()); + } + if ((sem_t*)ipcOperation->hNativeEvent != SEM_FAILED) { + sem_close((sem_t*)ipcOperation->hNativeEvent); + sem_unlink(eventName.c_str()); + } + return false; + } #else #error "Unsupported platform" #endif + handleInternal->registed_operations.push_back(*ipcOperation); + return true; } @@ -415,16 +612,31 @@ bool ipc_server_write_shared_memory(IpcHandle_t hIpcServer, IpcOperation_t ipcOp return false; __internal__IpcHandle_t* handleInternal = (__internal__IpcHandle_t*)hIpcServer; -#if _WIN32 +#if IPC_OS_WIN32 DWORD ret = WaitForSingleObject((HANDLE)ipcOperation.hNativeMutex, 10); if (ret == WAIT_TIMEOUT) { - ReleaseMutex((HANDLE)ipcOperation.hNativeMutex); return false; } void* pDest = (char*)handleInternal->pSharedMemory + IPC_FUNCTION_ARGS_TOTAL_SIZE + ipcOperation.dwSharedMemoryOffset; memcpy(pDest, pSrc, destSize); SetEvent((HANDLE)ipcOperation.hNativeEvent); ReleaseMutex((HANDLE)ipcOperation.hNativeMutex); +#elif IPC_OS_LINUX + sem_t* semMtx = (sem_t*)ipcOperation.hNativeMutex; + sem_t* semEvt = (sem_t*)ipcOperation.hNativeEvent; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 10000000; // 10ms + if (ts.tv_nsec >= 1000000000) { ts.tv_sec += 1; ts.tv_nsec -= 1000000000; } + + if (sem_timedwait(semMtx, &ts) != 0) { + return false; + } + void* pDest = (char*)handleInternal->pSharedMemory + IPC_FUNCTION_ARGS_TOTAL_SIZE + ipcOperation.dwSharedMemoryOffset; + memcpy(pDest, pSrc, destSize); + sem_post(semEvt); + sem_post(semMtx); #else #error "Unsupported platform" #endif @@ -440,20 +652,29 @@ bool ipc_client_read_shared_memory(IpcHandle_t hIpcClient, IpcOperation_t ipcOpe return false; __internal__IpcHandle_t* handleInternal = (__internal__IpcHandle_t*)hIpcClient; -#if _WIN32 - DWORD ret = WaitForSingleObject((HANDLE)ipcOperation.hNativeEvent, 10); +#if IPC_OS_WIN32 + DWORD ret = WaitForSingleObject((HANDLE)ipcOperation.hNativeMutex, 10); if (ret == WAIT_TIMEOUT) { - ReleaseMutex((HANDLE)ipcOperation.hNativeMutex); - return false; - } - ret = WaitForSingleObject((HANDLE)ipcOperation.hNativeMutex, 10); - if (ret == WAIT_TIMEOUT) { - ReleaseMutex((HANDLE)ipcOperation.hNativeMutex); return false; } void* pSrc = (char*)handleInternal->pSharedMemory + IPC_FUNCTION_ARGS_TOTAL_SIZE + ipcOperation.dwSharedMemoryOffset; memcpy(pDest, pSrc, destSize); ReleaseMutex((HANDLE)ipcOperation.hNativeMutex); +#elif IPC_OS_LINUX + sem_t* semMtx = (sem_t*)ipcOperation.hNativeMutex; + sem_t* semEvt = (sem_t*)ipcOperation.hNativeEvent; + + struct timespec ts; + clock_gettime(CLOCK_REALTIME, &ts); + ts.tv_nsec += 10000000; + if (ts.tv_nsec >= 1000000000) { ts.tv_sec += 1; ts.tv_nsec -= 1000000000; } + + if (sem_timedwait(semMtx, &ts) != 0) { + return false; + } + void* pSrc = (char*)handleInternal->pSharedMemory + IPC_FUNCTION_ARGS_TOTAL_SIZE + ipcOperation.dwSharedMemoryOffset; + memcpy(pDest, pSrc, destSize); + sem_post(semMtx); #else #error "Unsupported platform" #endif @@ -461,5 +682,9 @@ bool ipc_client_read_shared_memory(IpcHandle_t hIpcClient, IpcOperation_t ipcOpe return true; } +#if IPC_OS_LINUX +#undef IPC_S_RW_ALL +#endif + #endif // IPC_IMPLEMENTATION #endif // HEKKY_IPC_INC \ No newline at end of file