-
Notifications
You must be signed in to change notification settings - Fork 13
Core Internals
Lunae Mons Research edited this page Sep 1, 2025
·
2 revisions
This page provides detailed implementation insights into the core system initialization, module loading mechanics, configuration management internals, and platform-specific backend implementations.
The application entry point orchestrates the complete startup sequence:
int sdrpp_main(int argc, char* argv[]) {
// 1. ARGUMENT PARSING
parseCommandLineArgs(argc, argv);
// 2. ROOT PATH DETERMINATION
determineRootPaths();
// 3. LOGGING INITIALIZATION
initializeLogging();
// 4. CONFIGURATION SYSTEM STARTUP
initializeConfigSystem();
// 5. BACKEND INITIALIZATION
backend::init(selectedBackend);
// 6. CORE SYSTEM INITIALIZATION
MainWindow::init();
// 7. MODULE DISCOVERY AND LOADING
loadAllModules();
// 8. POST-INITIALIZATION
runPostInit();
// 9. MAIN RENDER LOOP
return backend::renderLoop();
}struct CommandLineArgs {
std::string rootPath = "";
std::string configPath = "";
std::string resourcePath = "";
bool serverMode = false;
bool autoStart = false;
int logLevel = 1; // INFO level
};
void parseCommandLineArgs(int argc, char* argv[]) {
for (int i = 1; i < argc; i++) {
std::string arg = argv[i];
if (arg == "--root" && i + 1 < argc) {
options.rootPath = argv[++i];
}
else if (arg == "--cfg" && i + 1 < argc) {
options.configPath = argv[++i];
}
else if (arg == "--res" && i + 1 < argc) {
options.resourcePath = argv[++i];
}
else if (arg == "--server") {
options.serverMode = true;
}
else if (arg == "--autostart") {
options.autoStart = true;
}
else if (arg == "--log-level" && i + 1 < argc) {
options.logLevel = std::stoi(argv[++i]);
}
else {
flog::warn("Unknown argument: {}", arg);
}
}
}Critical: Different algorithms for different platforms:
void determineRootPaths() {
if (!options.rootPath.empty()) {
// Explicit path from command line
rootPath = std::filesystem::absolute(options.rootPath);
}
else {
// Platform-specific automatic detection
#ifdef _WIN32
rootPath = getExecutableDirectory();
#elif defined(__APPLE__)
#ifdef IS_MACOS_BUNDLE
// App bundle: Resources directory
rootPath = getBundleResourcesPath();
#else
// Development build: executable directory
rootPath = getExecutableDirectory();
#endif
#elif defined(__ANDROID__)
// Android: app-specific directory
rootPath = getAndroidAssetPath();
#else
// Linux: check multiple locations
if (std::filesystem::exists("/usr/share/sdrpp")) {
rootPath = "/usr/share/sdrpp";
} else {
rootPath = getExecutableDirectory();
}
#endif
}
// Validate root path
if (!std::filesystem::exists(rootPath / "res")) {
flog::error("Invalid root path: {}", rootPath.string());
throw std::runtime_error("Resource directory not found");
}
flog::info("Root path: {}", rootPath.string());
}
std::filesystem::path getBundleResourcesPath() {
#ifdef __APPLE__
char execPath[PATH_MAX];
uint32_t size = PATH_MAX;
if (_NSGetExecutablePath(execPath, &size) == 0) {
std::filesystem::path exePath = execPath;
// From: MyApp.app/Contents/MacOS/sdrpp_ce
// To: MyApp.app/Contents/Resources
return exePath.parent_path().parent_path() / "Resources";
}
#endif
return std::filesystem::current_path();
}Schema-first design: All valid keys must be defined in core.cpp:
void createDefaultConfig() {
// Core system configuration
defConfig["fftSize"] = 8192;
defConfig["fftRate"] = 20;
defConfig["fftWindow"] = 1; // Blackman
defConfig["fftHold"] = false;
defConfig["fftHoldSpeed"] = 60;
// Audio configuration
defConfig["sampleRate"] = 48000.0;
defConfig["volume"] = 1.0;
defConfig["showWaterfall"] = true;
defConfig["waterfallHeight"] = 300;
// UI configuration
defConfig["showMenu"] = true;
defConfig["menuWidth"] = 300.0;
defConfig["fullscreen"] = false;
defConfig["maximized"] = false;
defConfig["windowSize"]["width"] = 1280;
defConfig["windowSize"]["height"] = 720;
// Source configuration
defConfig["source"] = "";
defConfig["frequency"] = 100000000.0; // 100 MHz
defConfig["centerTuning"] = false;
// Module configuration sections
defConfig["modules"]["audio_sink"]["enabled"] = true;
defConfig["modules"]["file_source"]["enabled"] = true;
defConfig["modules"]["scanner"]["enabled"] = false;
// Module-specific defaults
addModuleDefaults();
}
void addModuleDefaults() {
// Source modules
defConfig["rtlSdr"]["gain"] = 20.0;
defConfig["rtlSdr"]["sampleRate"] = 2048000.0;
defConfig["rtlSdr"]["directSampling"] = 0;
defConfig["rtlSdr"]["rtlAgc"] = false;
defConfig["rtlSdr"]["tunerAgc"] = false;
defConfig["hackRF"]["gain"] = 20.0;
defConfig["hackRF"]["sampleRate"] = 10000000.0;
defConfig["hackRF"]["bandwidth"] = 10000000.0;
defConfig["hackRF"]["amp"] = false;
defConfig["hackRF"]["antenna"] = false;
// Decoder modules
defConfig["radio"]["selectedDemod"] = "WFM";
defConfig["radio"]["frequency"] = 100000000.0;
defConfig["radio"]["bandwidth"] = 200000.0;
defConfig["radio"]["squelchLevel"] = -100.0;
defConfig["radio"]["volume"] = 1.0;
// Display settings
defConfig["colorMap"] = "Turbo";
defConfig["fftMin"] = -120.0;
defConfig["fftMax"] = 0.0;
defConfig["waterfallMin"] = -120.0;
defConfig["waterfallMax"] = 0.0;
// Band plan
defConfig["bandPlan"] = "General";
defConfig["bandPlanEnabled"] = true;
defConfig["bandPlanPos"] = 0; // Top
}Critical Process: Invalid keys are automatically removed to prevent corruption:
void ConfigManager::load() {
std::lock_guard<std::recursive_mutex> lock(mtx);
try {
// Load configuration file
std::ifstream file(configPath);
if (!file.is_open()) {
flog::warn("Config file not found, using defaults");
conf = defConfig;
return;
}
json loadedConfig;
file >> loadedConfig;
file.close();
// Perform configuration repair
conf = repairConfig(loadedConfig, defConfig);
flog::info("Configuration loaded and repaired");
} catch (std::exception& e) {
flog::error("Config load failed: {}", e.what());
flog::warn("Using default configuration");
conf = defConfig;
}
}
json ConfigManager::repairConfig(const json& config, const json& def) {
json repairedConfig;
bool anyRepairs = false;
// Copy all valid keys from default config
for (auto& [key, defaultValue] : def.items()) {
if (config.contains(key)) {
if (config[key].type() == defaultValue.type()) {
// Type matches - copy value
if (defaultValue.is_object()) {
// Recursively repair nested objects
repairedConfig[key] = repairConfig(config[key], defaultValue);
} else {
repairedConfig[key] = config[key];
}
} else {
// Type mismatch - use default
flog::warn("Config type mismatch for '{}', using default", key);
repairedConfig[key] = defaultValue;
anyRepairs = true;
}
} else {
// Missing key - add default
flog::info("Adding missing config key '{}'", key);
repairedConfig[key] = defaultValue;
anyRepairs = true;
}
}
// Report unused keys (will be discarded)
for (auto& [key, value] : config.items()) {
if (!def.contains(key)) {
flog::warn("Unused key in config '{}', removing", key);
anyRepairs = true;
}
}
if (anyRepairs) {
flog::info("Configuration repaired - {} changes made",
getRepairCount(config, repairedConfig));
}
return repairedConfig;
}class ConfigManager {
mutable std::recursive_mutex mtx;
json conf;
json defConfig;
std::atomic<bool> autoSaveEnabled{false};
std::atomic<bool> configDirty{false};
std::thread autoSaveThread;
public:
void acquire() const {
mtx.lock();
}
void release(bool markDirty = false) const {
if (markDirty) {
configDirty.store(true, std::memory_order_relaxed);
}
mtx.unlock();
}
void enableAutoSave() {
autoSaveEnabled.store(true);
autoSaveThread = std::thread([this]() {
while (autoSaveEnabled.load()) {
std::this_thread::sleep_for(std::chrono::seconds(1));
if (configDirty.exchange(false)) {
// Config changed - save it
std::lock_guard<std::recursive_mutex> lock(mtx);
saveToFile();
}
}
});
}
void disableAutoSave() {
autoSaveEnabled.store(false);
if (autoSaveThread.joinable()) {
autoSaveThread.join();
}
// Final save
if (configDirty.load()) {
std::lock_guard<std::recursive_mutex> lock(mtx);
saveToFile();
}
}
private:
void saveToFile() {
try {
std::ofstream file(configPath);
file << conf.dump(4); // Pretty print with 4-space indent
file.close();
flog::debug("Configuration auto-saved");
} catch (std::exception& e) {
flog::error("Auto-save failed: {}", e.what());
}
}
};void ModuleManager::discoverModules() {
std::vector<std::filesystem::path> searchPaths = {
rootPath / "modules",
rootPath / "plugins",
#ifdef __linux__
"/usr/lib/sdrpp/plugins",
"/usr/local/lib/sdrpp/plugins",
#elif defined(_WIN32)
rootPath / "plugins",
#elif defined(__APPLE__)
rootPath / "PlugIns", // macOS bundle convention
#endif
};
for (const auto& searchPath : searchPaths) {
if (!std::filesystem::exists(searchPath)) {
continue;
}
flog::info("Scanning for modules in: {}", searchPath.string());
for (const auto& entry : std::filesystem::directory_iterator(searchPath)) {
if (!entry.is_regular_file()) continue;
std::string filename = entry.path().filename().string();
std::string extension = entry.path().extension().string();
// Platform-specific library extensions
bool isLibrary = false;
#ifdef _WIN32
isLibrary = (extension == ".dll");
#elif defined(__APPLE__)
isLibrary = (extension == ".dylib");
#else
isLibrary = (extension == ".so");
#endif
if (isLibrary) {
discoverModule(entry.path());
}
}
}
flog::info("Found {} modules", availableModules.size());
}bool ModuleManager::loadModule(const std::filesystem::path& path) {
std::string pathStr = path.string();
// Load dynamic library
#ifdef _WIN32
HMODULE handle = LoadLibraryA(pathStr.c_str());
if (!handle) {
DWORD error = GetLastError();
flog::error("Failed to load module '{}': Windows error {}",
pathStr, error);
return false;
}
#else
void* handle = dlopen(pathStr.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle) {
flog::error("Failed to load module '{}': {}", pathStr, dlerror());
return false;
}
#endif
// Resolve required symbols
Module_t module;
module.handle = handle;
module.path = path;
if (!resolveModuleSymbols(module)) {
flog::error("Module '{}' missing required symbols", pathStr);
unloadModule(handle);
return false;
}
// Get module information
try {
module.info = module._INFO_();
if (!module.info) {
flog::error("Module '{}' returned null info", pathStr);
unloadModule(handle);
return false;
}
flog::info("Loaded module: {} v{} by {}",
module.info->name, module.info->version, module.info->author);
} catch (std::exception& e) {
flog::error("Module '{}' info query failed: {}", pathStr, e.what());
unloadModule(handle);
return false;
}
// Initialize module
try {
module._INIT_();
loadedModules[module.info->name] = module;
return true;
} catch (std::exception& e) {
flog::error("Module '{}' initialization failed: {}", pathStr, e.what());
unloadModule(handle);
return false;
}
}bool ModuleManager::resolveModuleSymbols(Module_t& module) {
// Required symbols for all modules
const char* requiredSymbols[] = {
"_INFO_",
"_INIT_",
"_CREATE_INSTANCE_",
"_DELETE_INSTANCE_",
"_END_"
};
#ifdef _WIN32
module._INFO_ = (ModuleInfo_t*(*)())GetProcAddress(
(HMODULE)module.handle, "_INFO_");
module._INIT_ = (void(*)())GetProcAddress(
(HMODULE)module.handle, "_INIT_");
module._CREATE_INSTANCE_ = (ModuleManager::Instance*(*)(std::string))GetProcAddress(
(HMODULE)module.handle, "_CREATE_INSTANCE_");
module._DELETE_INSTANCE_ = (void(*)(ModuleManager::Instance*))GetProcAddress(
(HMODULE)module.handle, "_DELETE_INSTANCE_");
module._END_ = (void(*)())GetProcAddress(
(HMODULE)module.handle, "_END_");
#else
module._INFO_ = (ModuleInfo_t*(*)())dlsym(module.handle, "_INFO_");
module._INIT_ = (void(*)())dlsym(module.handle, "_INIT_");
module._CREATE_INSTANCE_ = (ModuleManager::Instance*(*)(std::string))dlsym(
module.handle, "_CREATE_INSTANCE_");
module._DELETE_INSTANCE_ = (void(*)(ModuleManager::Instance*))dlsym(
module.handle, "_DELETE_INSTANCE_");
module._END_ = (void(*)())dlsym(module.handle, "_END_");
#endif
// Verify all symbols resolved
if (!module._INFO_ || !module._INIT_ || !module._CREATE_INSTANCE_ ||
!module._DELETE_INSTANCE_ || !module._END_) {
flog::error("Module missing required exports:");
if (!module._INFO_) flog::error(" - _INFO_");
if (!module._INIT_) flog::error(" - _INIT_");
if (!module._CREATE_INSTANCE_) flog::error(" - _CREATE_INSTANCE_");
if (!module._DELETE_INSTANCE_) flog::error(" - _DELETE_INSTANCE_");
if (!module._END_) flog::error(" - _END_");
return false;
}
return true;
}ModuleManager::Instance* ModuleManager::createInstance(const std::string& module,
const std::string& name) {
auto moduleIt = loadedModules.find(module);
if (moduleIt == loadedModules.end()) {
flog::error("Module '{}' not loaded", module);
return nullptr;
}
Module_t& mod = moduleIt->second;
// Check instance limits
if (mod.info->maxInstances >= 0) {
int currentCount = 0;
for (const auto& [instName, instance] : instances) {
if (instance.module.info->name == module) {
currentCount++;
}
}
if (currentCount >= mod.info->maxInstances) {
flog::error("Module '{}' instance limit ({}) exceeded",
module, mod.info->maxInstances);
return nullptr;
}
}
try {
// Create instance
Instance* inst = mod._CREATE_INSTANCE_(name);
if (!inst) {
flog::error("Module '{}' failed to create instance '{}'", module, name);
return nullptr;
}
// Register instance
InstanceInfo_t info;
info.module = mod;
info.instance = inst;
info.name = name;
instances[name] = info;
flog::info("Created module instance: {} ({})", name, module);
return inst;
} catch (std::exception& e) {
flog::error("Exception creating instance '{}' of module '{}': {}",
name, module, e.what());
return nullptr;
}
}enum class ModuleLoadError {
FILE_NOT_FOUND,
INVALID_LIBRARY,
MISSING_SYMBOLS,
INIT_FAILED,
ABI_MISMATCH,
DEPENDENCY_MISSING,
VERSION_INCOMPATIBLE
};
ModuleLoadError diagnoseLoadFailure(const std::filesystem::path& path) {
if (!std::filesystem::exists(path)) {
return ModuleLoadError::FILE_NOT_FOUND;
}
// Try to load library
#ifdef _WIN32
HMODULE testHandle = LoadLibraryA(path.string().c_str());
if (!testHandle) {
DWORD error = GetLastError();
switch (error) {
case ERROR_MOD_NOT_FOUND:
return ModuleLoadError::DEPENDENCY_MISSING;
case ERROR_BAD_EXE_FORMAT:
return ModuleLoadError::ABI_MISMATCH;
default:
return ModuleLoadError::INVALID_LIBRARY;
}
}
// Check for required symbols
if (!GetProcAddress(testHandle, "_INFO_")) {
FreeLibrary(testHandle);
return ModuleLoadError::MISSING_SYMBOLS;
}
FreeLibrary(testHandle);
#else
void* testHandle = dlopen(path.string().c_str(), RTLD_LAZY);
if (!testHandle) {
std::string error = dlerror();
if (error.find("wrong ELF class") != std::string::npos ||
error.find("invalid ELF header") != std::string::npos) {
return ModuleLoadError::ABI_MISMATCH;
}
if (error.find("cannot open shared object file") != std::string::npos) {
return ModuleLoadError::DEPENDENCY_MISSING;
}
return ModuleLoadError::INVALID_LIBRARY;
}
if (!dlsym(testHandle, "_INFO_")) {
dlclose(testHandle);
return ModuleLoadError::MISSING_SYMBOLS;
}
dlclose(testHandle);
#endif
return ModuleLoadError::INIT_FAILED; // Symbols exist but init fails
}
void reportLoadError(const std::filesystem::path& path, ModuleLoadError error) {
std::string filename = path.filename().string();
switch (error) {
case ModuleLoadError::FILE_NOT_FOUND:
flog::error("Module file not found: {}", filename);
break;
case ModuleLoadError::DEPENDENCY_MISSING:
flog::error("Module '{}' has missing dependencies", filename);
flog::info("Try installing required libraries or check LD_LIBRARY_PATH");
break;
case ModuleLoadError::ABI_MISMATCH:
flog::error("Module '{}' ABI mismatch (wrong architecture?)", filename);
flog::info("Ensure module was compiled for this platform");
break;
case ModuleLoadError::MISSING_SYMBOLS:
flog::error("Module '{}' missing required SDR++ exports", filename);
flog::info("Module may not be compatible with this SDR++ version");
break;
case ModuleLoadError::INIT_FAILED:
flog::error("Module '{}' initialization failed", filename);
break;
default:
flog::error("Module '{}' failed to load", filename);
break;
}
}Critical: FFTW requires careful initialization for thread safety:
void MainWindow::init() {
flog::info("Initializing main window...");
// 1. FFTW INITIALIZATION (must be first)
initializeFFTW();
// 2. UI SYSTEM SETUP
initializeUI();
// 3. SIGNAL PATH INITIALIZATION
initializeSignalPath();
// 4. MODULE LOADING
loadAndInitializeModules();
// 5. POST-INITIALIZATION
runPostInitialization();
flog::info("Main window initialized successfully");
}
void MainWindow::initializeFFTW() {
// Make FFTW thread-safe
if (fftwf_init_threads() == 0) {
flog::error("Failed to initialize FFTW threads");
throw std::runtime_error("FFTW thread initialization failed");
}
// Set number of threads (use half of available cores for FFTW)
int numThreads = std::max(1, (int)std::thread::hardware_concurrency() / 2);
fftwf_plan_with_nthreads(numThreads);
flog::info("FFTW initialized with {} threads", numThreads);
// Create FFT plans for common sizes
createFFTPlans();
}
void MainWindow::createFFTPlans() {
// Common FFT sizes used in SDR++
std::vector<int> fftSizes = {1024, 2048, 4096, 8192, 16384, 32768, 65536};
for (int size : fftSizes) {
// Create plans for both forward and inverse transforms
fftw_complex* in = (fftw_complex*)fftwf_malloc(sizeof(fftw_complex) * size);
fftw_complex* out = (fftw_complex*)fftwf_malloc(sizeof(fftw_complex) * size);
if (in && out) {
// Forward plan (time domain -> frequency domain)
fftwf_plan forwardPlan = fftwf_plan_dft_1d(size, in, out,
FFTW_FORWARD, FFTW_MEASURE);
// Inverse plan (frequency domain -> time domain)
fftwf_plan inversePlan = fftwf_plan_dft_1d(size, in, out,
FFTW_BACKWARD, FFTW_MEASURE);
if (forwardPlan) {
fftPlans[size].forward = forwardPlan;
flog::debug("Created forward FFT plan for size {}", size);
}
if (inversePlan) {
fftPlans[size].inverse = inversePlan;
flog::debug("Created inverse FFT plan for size {}", size);
}
}
if (in) fftwf_free(in);
if (out) fftwf_free(out);
}
flog::info("Created FFT plans for {} sizes", fftPlans.size());
}void MainWindow::initializeSignalPath() {
try {
// Initialize signal path components in dependency order
// 1. Source manager (hardware interfaces)
sigpath::sourceManager.init();
// 2. VFO manager (virtual receivers)
sigpath::vfoManager.init();
// 3. Sink manager (audio outputs)
sigpath::sinkManager.init();
// 4. IQ frontend (central DSP hub)
sigpath::iqFrontEnd.init();
// 5. Signal path coordinator
sigpath::signalPath.init();
flog::info("Signal path initialized");
} catch (std::exception& e) {
flog::error("Signal path initialization failed: {}", e.what());
throw;
}
}
void MainWindow::initializeUI() {
try {
// 1. Menu system
gui::menu.init();
// 2. Waterfall widget
gui::waterfall.init();
// 3. Frequency selector
gui::freqSelect.init();
// 4. Style and theme
gui::style.init();
// 5. Core menu entries
registerCoreMenus();
flog::info("UI system initialized");
} catch (std::exception& e) {
flog::error("UI initialization failed: {}", e.what());
throw;
}
}void MainWindow::loadAndInitializeModules() {
// 1. DISCOVER AVAILABLE MODULES
core::moduleManager.discoverModules();
// 2. LOAD ENABLED MODULES
loadEnabledModules();
// 3. CREATE DEFAULT INSTANCES
createDefaultInstances();
// 4. VERIFY CRITICAL MODULES
verifyCriticalModules();
}
void MainWindow::loadEnabledModules() {
core::configManager.acquire();
if (core::configManager.conf.contains("modules")) {
auto& modulesConfig = core::configManager.conf["modules"];
for (auto& [moduleName, moduleConfig] : modulesConfig.items()) {
bool enabled = true;
if (moduleConfig.contains("enabled")) {
enabled = moduleConfig["enabled"];
}
if (enabled) {
if (core::moduleManager.loadModule(moduleName)) {
flog::info("Loaded module: {}", moduleName);
} else {
flog::warn("Failed to load enabled module: {}", moduleName);
}
}
}
}
core::configManager.release();
}
void MainWindow::createDefaultInstances() {
// Create default instances for singleton modules
auto& loadedModules = core::moduleManager.getLoadedModules();
for (auto& [name, module] : loadedModules) {
if (module.info->maxInstances == 1) {
// Singleton module - create default instance
auto instance = core::moduleManager.createInstance(name, name);
if (instance) {
flog::info("Created default instance for: {}", name);
}
}
}
}
void MainWindow::verifyCriticalModules() {
// Check for essential modules
std::vector<std::string> criticalModules = {
"audio_sink", // Required for audio output
"radio" // Required for basic radio functionality
};
for (const std::string& moduleName : criticalModules) {
if (!core::moduleManager.isModuleLoaded(moduleName)) {
flog::warn("Critical module '{}' not loaded - functionality may be limited",
moduleName);
}
}
// Verify at least one audio sink exists
if (sigpath::sinkManager.getProviders().empty()) {
flog::error("No audio sinks available - audio output disabled");
}
// Verify at least one source exists
if (sigpath::sourceManager.getSources().empty()) {
flog::warn("No signal sources available - only file input possible");
}
}Critical timing: Ensures all modules are loaded before cross-module dependencies:
void MainWindow::runPostInitialization() {
flog::info("Running post-initialization phase...");
// Call postInit() on all module instances
auto& instances = core::moduleManager.getInstances();
int successCount = 0;
int failureCount = 0;
for (auto& [name, instanceInfo] : instances) {
try {
instanceInfo.instance->postInit();
successCount++;
flog::debug("Post-init completed for: {}", name);
} catch (std::exception& e) {
flog::error("Post-init failed for '{}': {}", name, e.what());
failureCount++;
// Consider disabling failed module
instanceInfo.instance->disable();
}
}
flog::info("Post-initialization complete: {} success, {} failures",
successCount, failureCount);
// Register core UI components after modules
registerCoreUI();
// Apply initial configuration
applyInitialConfiguration();
// Start auto-save
core::configManager.enableAutoSave();
}
void MainWindow::applyInitialConfiguration() {
core::configManager.acquire();
// Apply window settings
if (core::configManager.conf.contains("windowSize")) {
int width = core::configManager.conf["windowSize"]["width"];
int height = core::configManager.conf["windowSize"]["height"];
backend::setWindowSize(width, height);
}
// Apply frequency setting
if (core::configManager.conf.contains("frequency")) {
double frequency = core::configManager.conf["frequency"];
gui::freqSelect.setFrequency(frequency);
}
// Apply source selection
if (core::configManager.conf.contains("source")) {
std::string source = core::configManager.conf["source"];
if (!source.empty() && sigpath::sourceManager.sourceExists(source)) {
sigpath::sourceManager.selectSource(source);
}
}
// Auto-start if requested
if (core::configManager.conf.contains("autoStart") &&
core::configManager.conf["autoStart"]) {
setPlaying(true);
}
core::configManager.release();
}namespace backend {
static GLFWwindow* window = nullptr;
static bool vsyncEnabled = true;
static std::function<void()> drawCallback;
bool init(BackendType type) {
if (type != BACKEND_GLFW) return false;
// Initialize GLFW
if (!glfwInit()) {
flog::error("Failed to initialize GLFW");
return false;
}
// Configure OpenGL context
#ifdef __APPLE__
// macOS requires specific OpenGL version
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE);
#else
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
// Window hints
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE); // Show after setup
// Create window
window = glfwCreateWindow(1280, 720, "SDR++CE", nullptr, nullptr);
if (!window) {
flog::error("Failed to create GLFW window");
glfwTerminate();
return false;
}
// Set up OpenGL context
glfwMakeContextCurrent(window);
// Load OpenGL functions
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
flog::error("Failed to load OpenGL functions");
return false;
}
// Configure VSync
glfwSwapInterval(vsyncEnabled ? 1 : 0);
// Set callbacks
setupCallbacks();
// Initialize ImGui
initializeImGui();
// Load and set icon
loadWindowIcon();
// Show window
glfwShowWindow(window);
flog::info("GLFW backend initialized successfully");
return true;
}
void setupCallbacks() {
// Window resize callback
glfwSetWindowSizeCallback(window, [](GLFWwindow* win, int width, int height) {
glViewport(0, 0, width, height);
// Save window size to config
core::configManager.acquire();
core::configManager.conf["windowSize"]["width"] = width;
core::configManager.conf["windowSize"]["height"] = height;
core::configManager.release(true);
});
// Key callback for global shortcuts
glfwSetKeyCallback(window, [](GLFWwindow* win, int key, int scancode, int action, int mods) {
// Let ImGui handle input first
ImGuiIO& io = ImGui::GetIO();
if (io.WantCaptureKeyboard) return;
// Global shortcuts
if (action == GLFW_PRESS) {
switch (key) {
case GLFW_KEY_SPACE:
if (mods == 0) {
// Toggle play/stop
MainWindow::togglePlaying();
}
break;
case GLFW_KEY_F11:
if (mods == 0) {
// Toggle fullscreen
toggleFullscreen();
}
break;
}
}
});
// Error callback
glfwSetErrorCallback([](int error, const char* description) {
flog::error("GLFW Error {}: {}", error, description);
});
}
int renderLoop() {
while (!glfwWindowShouldClose(window)) {
// Poll events
glfwPollEvents();
// Start ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// Render application
if (drawCallback) {
drawCallback();
}
// Render ImGui
ImGui::Render();
int display_w, display_h;
glfwGetFramebufferSize(window, &display_w, &display_h);
glViewport(0, 0, display_w, display_h);
glClear(GL_COLOR_BUFFER_BIT);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// Swap buffers
glfwSwapBuffers(window);
}
// Cleanup
cleanup();
return 0;
}
}namespace backend {
static EGLDisplay eglDisplay = EGL_NO_DISPLAY;
static EGLContext eglContext = EGL_NO_CONTEXT;
static EGLSurface eglSurface = EGL_NO_SURFACE;
static ANativeWindow* nativeWindow = nullptr;
bool initAndroid(ANativeWindow* window) {
nativeWindow = window;
// Initialize EGL
eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (eglDisplay == EGL_NO_DISPLAY) {
flog::error("Failed to get EGL display");
return false;
}
if (!eglInitialize(eglDisplay, nullptr, nullptr)) {
flog::error("Failed to initialize EGL");
return false;
}
// Configure EGL
const EGLint configAttribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_ALPHA_SIZE, 8,
EGL_DEPTH_SIZE, 16,
EGL_NONE
};
EGLConfig config;
EGLint numConfigs;
if (!eglChooseConfig(eglDisplay, configAttribs, &config, 1, &numConfigs)) {
flog::error("Failed to choose EGL config");
return false;
}
// Create surface
eglSurface = eglCreateWindowSurface(eglDisplay, config, nativeWindow, nullptr);
if (eglSurface == EGL_NO_SURFACE) {
flog::error("Failed to create EGL surface");
return false;
}
// Create context
const EGLint contextAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
eglContext = eglCreateContext(eglDisplay, config, EGL_NO_CONTEXT, contextAttribs);
if (eglContext == EGL_NO_CONTEXT) {
flog::error("Failed to create EGL context");
return false;
}
// Make current
if (!eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext)) {
flog::error("Failed to make EGL context current");
return false;
}
// Initialize ImGui for Android
initializeImGuiAndroid();
flog::info("Android backend initialized successfully");
return true;
}
// JNI interface for keyboard input
extern "C" {
JNIEXPORT void JNICALL
Java_com_sdrpp_sdrpp_MainActivity_nativeKeyInput(JNIEnv* env, jobject thiz,
jint keycode, jboolean pressed) {
// Forward to ImGui
ImGuiIO& io = ImGui::GetIO();
if (pressed) {
io.KeysDown[keycode] = true;
} else {
io.KeysDown[keycode] = false;
}
}
JNIEXPORT void JNICALL
Java_com_sdrpp_sdrpp_MainActivity_nativeTextInput(JNIEnv* env, jobject thiz,
jstring text) {
const char* textChars = env->GetStringUTFChars(text, nullptr);
ImGuiIO& io = ImGui::GetIO();
io.AddInputCharactersUTF8(textChars);
env->ReleaseStringUTFChars(text, textChars);
}
}
}