Skip to content

Core Internals

Lunae Mons Research edited this page Sep 1, 2025 · 2 revisions

Core Internals

This page provides detailed implementation insights into the core system initialization, module loading mechanics, configuration management internals, and platform-specific backend implementations.

Application Startup Deep Dive

sdrpp_main Function Flow

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();
}

Argument Parsing Implementation

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);
        }
    }
}

Root Path Resolution Logic

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();
}

Configuration System Internals

Default Configuration Schema Creation

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
}

Configuration Repair Mechanism

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;
}

Thread-Safe Configuration Access

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());
        }
    }
};

Module Loading Deep Dive

Module Discovery Process

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());
}

Dynamic Library Loading

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;
    }
}

Symbol Resolution

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;
}

Module Instance Management

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;
    }
}

Error Handling During Module Loading

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;
    }
}

MainWindow Initialization Deep Dive

FFTW Setup and Thread Safety

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());
}

Global Object Initialization

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;
    }
}

Module Loading Orchestration

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");
    }
}

PostInit Phase Implementation

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();
}

Platform-Specific Backend Implementation

GLFW Backend (Desktop)

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;
    }
}

Android Backend

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);
        }
    }
}

Clone this wiki locally