commit 038f4f471bc5b4a81cb907389d87b4255381b6bb
parent fa35643a5cf7655f67d065ca1bfd6e51ca64fd48
Author: Bradley Gannon <bradley@bradleygannon.com>
Date: Tue, 1 Sep 2026 19:21:24 -0400
Add initial SDR++ module with Rust internals
Diffstat:
8 files changed, 290 insertions(+), 3 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1 +1,4 @@
/target
+/civ_module/target
+/civ_module/build
+/civ_module/dist
diff --git a/civ_module/Cargo.lock b/civ_module/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "civ_sync"
+version = "0.1.0"
diff --git a/civ_module/Cargo.toml b/civ_module/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "civ_sync"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["staticlib"]
+
+[dependencies]
diff --git a/civ_module/Makefile b/civ_module/Makefile
@@ -0,0 +1,72 @@
+# Builds the civ_sync SDR++ module (Rust staticlib + C++ shim) and installs the
+# resulting .so into a local SDR++ config root for GUI loading.
+#
+# The SDR++ source location is configurable via the SDRPP_DIR environment
+# variable or `make SDRPP_DIR=...`; it defaults to ../.hidden/SDRPlusPlus.
+#
+# Run inside `nix develop` so cargo, cmake, g++, and the SDR++ deps are present.
+
+SHELL := bash
+
+CIV_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
+REPO_ROOT := $(abspath $(CIV_DIR)/..)
+
+SDRPP_DIR ?= $(abspath $(REPO_ROOT)/.hidden/SDRPlusPlus)
+SDRPP_BUILD := $(SDRPP_DIR)/build
+CORE_LIB := $(SDRPP_BUILD)/core/libsdrpp_core.so
+
+MOD_BUILD := $(CIV_DIR)/build
+MODULE_SO := $(MOD_BUILD)/civ_sync.so
+
+DIST_ROOT ?= $(CIV_DIR)/dist/root
+MODULES_DIR ?= $(DIST_ROOT)/modules
+INSTALLED_SO := $(MODULES_DIR)/civ_sync.so
+
+NPROC ?= $(shell nproc 2>/dev/null || echo 4)
+
+.PHONY: all module rust core install clean help
+
+all: module install
+
+module: rust $(CORE_LIB)
+ @echo "==> Configuring civ_sync module"
+ cmake -S "$(CIV_DIR)/shim" -B "$(MOD_BUILD)" \
+ -DCMAKE_BUILD_TYPE=Release -DSDRPP_DIR="$(SDRPP_DIR)"
+ @echo "==> Building civ_sync module"
+ cmake --build "$(MOD_BUILD)" -j$(NPROC)
+
+rust:
+ @echo "==> Building Rust staticlib"
+ cargo build --release --manifest-path "$(CIV_DIR)/Cargo.toml"
+
+$(CORE_LIB):
+ @echo "==> Configuring SDR++ (core only)"
+ @opts=""; for opt in $$(grep -oE 'option\(OPT_BUILD_[A-Z0-9_]+' "$(SDRPP_DIR)/CMakeLists.txt" | sed 's/option(//' | sort -u); do opts="$${opts} -D$${opt}=OFF"; done; \
+ cmake -S "$(SDRPP_DIR)" -B "$(SDRPP_BUILD)" -DCMAKE_BUILD_TYPE=Release $${opts}
+ @echo "==> Building sdrpp_core"
+ cmake --build "$(SDRPP_BUILD)" --target sdrpp_core -j$(NPROC)
+
+core: $(CORE_LIB)
+
+install: module
+ @echo "==> Installing module to $(MODULES_DIR)"
+ mkdir -p "$(MODULES_DIR)"
+ cp -f "$(MODULE_SO)" "$(INSTALLED_SO)"
+
+clean:
+ rm -rf "$(MOD_BUILD)" "$(DIST_ROOT)"
+
+help:
+ @printf '%s\n' \
+ 'Targets:' \
+ ' all build module + install into $(MODULES_DIR) (default)' \
+ ' module build $(MODULE_SO)' \
+ ' rust build the Rust staticlib' \
+ ' core build the SDR++ core library only' \
+ ' install copy $(MODULE_SO) into $(MODULES_DIR)' \
+ ' clean remove module build dir and installed output' \
+ '' \
+ 'Variables (env or `make VAR=...`):' \
+ ' SDRPP_DIR SDR++ source root [default: $(SDRPP_DIR)]' \
+ ' MODULES_DIR module install dir [default: $(MODULES_DIR)]' \
+ ' NPROC parallel jobs [default: $(NPROC)]'
diff --git a/civ_module/shim/CMakeLists.txt b/civ_module/shim/CMakeLists.txt
@@ -0,0 +1,41 @@
+cmake_minimum_required(VERSION 3.13)
+project(civ_sync)
+
+set(SDRPP_DIR "" CACHE PATH "Path to the SDR++ source root")
+if (NOT SDRPP_DIR)
+ message(FATAL_ERROR "SDRPP_DIR must be set to the SDR++ source root")
+endif()
+
+set(CIV_STATICLIB "${CMAKE_CURRENT_SOURCE_DIR}/../target/release/libciv_sync.a"
+ CACHE FILEPATH "Path to the built Rust staticlib")
+if (NOT EXISTS "${CIV_STATICLIB}")
+ message(FATAL_ERROR "Rust staticlib not found at ${CIV_STATICLIB}; run 'cargo build --release' first")
+endif()
+
+set(SDRPP_CORE_LIB "${SDRPP_DIR}/build/core/libsdrpp_core.so"
+ CACHE FILEPATH "Path to the built libsdrpp_core.so")
+if (NOT EXISTS "${SDRPP_CORE_LIB}")
+ message(FATAL_ERROR "libsdrpp_core.so not found at ${SDRPP_CORE_LIB}; build the SDR++ core first")
+endif()
+
+find_package(OpenGL REQUIRED)
+
+file(GLOB SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp")
+
+add_library(${PROJECT_NAME} SHARED ${SRC})
+
+target_include_directories(${PROJECT_NAME} PRIVATE
+ "${SDRPP_DIR}/core/src"
+ "${SDRPP_DIR}/core/src/imgui"
+ ${OPENGL_INCLUDE_DIRS}
+)
+
+target_link_libraries(${PROJECT_NAME} PRIVATE
+ "${SDRPP_CORE_LIB}"
+ "${CIV_STATICLIB}"
+ pthread dl m
+)
+
+target_compile_options(${PROJECT_NAME} PRIVATE -std=c++17 -O2 -fPIC)
+
+set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "")
diff --git a/civ_module/shim/src/main.cpp b/civ_module/shim/src/main.cpp
@@ -0,0 +1,65 @@
+#include <imgui.h>
+#include <module.h>
+#include <gui/gui.h>
+
+extern "C" {
+ void* civ_create(const char* name);
+ void civ_destroy(void* ctx);
+ void civ_post_init(void* ctx);
+ void civ_enable(void* ctx);
+ void civ_disable(void* ctx);
+ bool civ_is_enabled(void* ctx);
+ const char* civ_status(void* ctx);
+}
+
+SDRPP_MOD_INFO{
+ /* Name: */ "civ_sync",
+ /* Description: */ "Sync an Icom IC-7300 (CI-V) with an SDR++ VFO",
+ /* Author: */ "vfo-sync",
+ /* Version: */ 0, 1, 0,
+ /* Max instances */ 1
+};
+
+class CivInstance : public ModuleManager::Instance {
+public:
+ CivInstance(std::string name) {
+ this->name = name;
+ rustCtx = civ_create(name.c_str());
+ gui::menu.registerEntry(name, menuHandler, this, this);
+ }
+
+ ~CivInstance() {
+ gui::menu.removeEntry(name);
+ civ_destroy(rustCtx);
+ }
+
+ void postInit() override { civ_post_init(rustCtx); }
+ void enable() override { civ_enable(rustCtx); }
+ void disable() override { civ_disable(rustCtx); }
+ bool isEnabled() override { return civ_is_enabled(rustCtx); }
+
+private:
+ static void menuHandler(void* ctx) {
+ CivInstance* _this = (CivInstance*)ctx;
+ ImGui::TextUnformatted(civ_status(_this->rustCtx));
+ }
+
+ std::string name;
+ void* rustCtx = nullptr;
+};
+
+MOD_EXPORT void _INIT_() {
+ // Nothing here
+}
+
+MOD_EXPORT ModuleManager::Instance* _CREATE_INSTANCE_(std::string name) {
+ return new CivInstance(name);
+}
+
+MOD_EXPORT void _DELETE_INSTANCE_(void* instance) {
+ delete (CivInstance*)instance;
+}
+
+MOD_EXPORT void _END_() {
+ // Nothing here
+}
diff --git a/civ_module/src/lib.rs b/civ_module/src/lib.rs
@@ -0,0 +1,81 @@
+#![allow(clippy::missing_safety_doc)]
+
+use std::ffi::{c_char, CStr, CString};
+
+pub struct CivCtx {
+ name: String,
+ status: CString,
+ enabled: bool,
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn civ_create(name: *const c_char) -> *mut CivCtx {
+ let name = if name.is_null() {
+ "<unnamed>".to_string()
+ } else {
+ unsafe { CStr::from_ptr(name) }
+ .to_string_lossy()
+ .into_owned()
+ };
+
+ let msg = format!("Rust core alive (instance: {})", name);
+ eprintln!("[civ_sync] {}", msg);
+
+ let ctx = CivCtx {
+ name,
+ status: CString::new(msg).unwrap(),
+ enabled: false,
+ };
+ Box::into_raw(Box::new(ctx))
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn civ_destroy(ctx: *mut CivCtx) {
+ if ctx.is_null() {
+ return;
+ }
+ let ctx = Box::from_raw(ctx);
+ eprintln!("[civ_sync] Rust core destroyed (instance: {})", ctx.name);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn civ_post_init(ctx: *mut CivCtx) {
+ if ctx.is_null() {
+ return;
+ }
+ eprintln!("[civ_sync] Rust core post-init (instance: {})", (*ctx).name);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn civ_enable(ctx: *mut CivCtx) {
+ if ctx.is_null() {
+ return;
+ }
+ (*ctx).enabled = true;
+ eprintln!("[civ_sync] Rust core enabled (instance: {})", (*ctx).name);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn civ_disable(ctx: *mut CivCtx) {
+ if ctx.is_null() {
+ return;
+ }
+ (*ctx).enabled = false;
+ eprintln!("[civ_sync] Rust core disabled (instance: {})", (*ctx).name);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn civ_is_enabled(ctx: *mut CivCtx) -> bool {
+ if ctx.is_null() {
+ return false;
+ }
+ (*ctx).enabled
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn civ_status(ctx: *mut CivCtx) -> *const c_char {
+ if ctx.is_null() {
+ return std::ptr::null();
+ }
+ (*ctx).status.as_ptr()
+}
diff --git a/flake.nix b/flake.nix
@@ -12,7 +12,14 @@
config = { };
overlays = [ ];
};
- libInputs = [ ];
+ libInputs = with stable; [
+ fftwFloat
+ glfw
+ libGL
+ stdenv.cc.cc
+ volk
+ zstd
+ ];
lib-path = with stable; lib.makeLibraryPath libInputs;
in
{
@@ -20,13 +27,15 @@
packages = with stable; [
cargo
clippy
+ cmake
+ gcc
+ pkg-config
rust-analyzer
rustc
rustfmt
- pkg-config
systemd
udev
- ];
+ ] ++ libInputs;
shellHook = ''
export "LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${lib-path}"