#include #include "imgui.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define STB_IMAGE_RESIZE_IMPLEMENTATION #include #include #include #ifdef _WIN32 #include #endif #ifndef INSTALL_PREFIX #ifdef __APPLE__ #define INSTALL_PREFIX "/usr/local" #else #define INSTALL_PREFIX "/usr" #endif #endif namespace core { ConfigManager configManager; ModuleManager moduleManager; ModuleComManager modComManager; CommandArgsParser args; void setInputSampleRate(double samplerate) { // Forward this to the server if (args["server"].b()) { server::setInputSampleRate(samplerate); return; } // Update IQ frontend input samplerate and get effective samplerate sigpath::iqFrontEnd.setSampleRate(samplerate); double effectiveSr = sigpath::iqFrontEnd.getEffectiveSamplerate(); // Reset zoom gui::waterfall.setBandwidth(effectiveSr); gui::waterfall.setViewOffset(0); gui::waterfall.setViewBandwidth(effectiveSr); gui::mainWindow.setViewBandwidthSlider(1.0); // Debug logs spdlog::info("New DSP samplerate: {0} (source samplerate is {1})", effectiveSr, samplerate); } }; // main int sdrpp_main(int argc, char* argv[]) { spdlog::info("SDR++ v" VERSION_STR); #ifdef IS_MACOS_BUNDLE // If this is a MacOS .app, CD to the correct directory auto execPath = std::filesystem::absolute(argv[0]); chdir(execPath.parent_path().string().c_str()); #endif // Define command line options and parse arguments core::args.defineAll(); if (core::args.parse(argc, argv) < 0) { return -1; } // Show help and exit if requested if (core::args["help"].b()) { core::args.showHelp(); return 0; } bool serverMode = (bool)core::args["server"]; #ifdef _WIN32 if (!core::args["con"].b() && !serverMode) { FreeConsole(); } #endif // Check root directory std::string root = (std::string)core::args["root"]; if (!std::filesystem::exists(root)) { spdlog::warn("Root directory {0} does not exist, creating it", root); if (!std::filesystem::create_directories(root)) { spdlog::error("Could not create root directory {0}", root); return -1; } } // Check that the path actually is a directory if (!std::filesystem::is_directory(root)) { spdlog::error("{0} is not a directory", root); return -1; } // ======== DEFAULT CONFIG ======== json defConfig; defConfig["bandColors"]["amateur"] = "#FF0000FF"; defConfig["bandColors"]["aviation"] = "#00FF00FF"; defConfig["bandColors"]["broadcast"] = "#0000FFFF"; defConfig["bandColors"]["marine"] = "#00FFFFFF"; defConfig["bandColors"]["military"] = "#FFFF00FF"; defConfig["bandPlan"] = "General"; defConfig["bandPlanEnabled"] = true; defConfig["bandPlanPos"] = 0; defConfig["centerTuning"] = false; defConfig["colorMap"] = "Classic"; defConfig["fftHold"] = false; defConfig["fftHoldSpeed"] = 60; defConfig["fastFFT"] = false; defConfig["fftHeight"] = 300; defConfig["fftRate"] = 20; defConfig["fftSize"] = 65536; defConfig["fftWindow"] = 1; defConfig["frequency"] = 100000000.0; defConfig["fullWaterfallUpdate"] = false; defConfig["max"] = 0.0; defConfig["maximized"] = false; defConfig["fullscreen"] = false; // Menu defConfig["menuElements"] = json::array(); defConfig["menuElements"][0]["name"] = "Source"; defConfig["menuElements"][0]["open"] = true; defConfig["menuElements"][1]["name"] = "Radio"; defConfig["menuElements"][1]["open"] = true; defConfig["menuElements"][2]["name"] = "Recorder"; defConfig["menuElements"][2]["open"] = true; defConfig["menuElements"][3]["name"] = "Sinks"; defConfig["menuElements"][3]["open"] = true; defConfig["menuElements"][3]["name"] = "Frequency Manager"; defConfig["menuElements"][3]["open"] = true; defConfig["menuElements"][4]["name"] = "VFO Color"; defConfig["menuElements"][4]["open"] = true; defConfig["menuElements"][5]["name"] = "Scripting"; defConfig["menuElements"][5]["open"] = false; defConfig["menuElements"][6]["name"] = "Band Plan"; defConfig["menuElements"][6]["open"] = true; defConfig["menuElements"][7]["name"] = "Display"; defConfig["menuElements"][7]["open"] = true; defConfig["menuWidth"] = 300; defConfig["min"] = -120.0; // Module instances defConfig["moduleInstances"]["Airspy Source"]["module"] = "airspy_source"; defConfig["moduleInstances"]["Airspy Source"]["enabled"] = true; defConfig["moduleInstances"]["AirspyHF+ Source"]["module"] = "airspyhf_source"; defConfig["moduleInstances"]["AirspyHF+ Source"]["enabled"] = true; defConfig["moduleInstances"]["BladeRF Source"]["module"] = "bladerf_source"; defConfig["moduleInstances"]["BladeRF Source"]["enabled"] = true; defConfig["moduleInstances"]["File Source"]["module"] = "file_source"; defConfig["moduleInstances"]["File Source"]["enabled"] = true; defConfig["moduleInstances"]["HackRF Source"]["module"] = "hackrf_source"; defConfig["moduleInstances"]["HackRF Source"]["enabled"] = true; defConfig["moduleInstances"]["LimeSDR Source"]["module"] = "limesdr_source"; defConfig["moduleInstances"]["LimeSDR Source"]["enabled"] = true; defConfig["moduleInstances"]["RFspace Source"]["module"] = "rfspace_source"; defConfig["moduleInstances"]["RFspace Source"]["enabled"] = true; defConfig["moduleInstances"]["RTL-SDR Source"]["module"] = "rtl_sdr_source"; defConfig["moduleInstances"]["RTL-SDR Source"]["enabled"] = true; defConfig["moduleInstances"]["RTL-TCP Source"]["module"] = "rtl_tcp_source"; defConfig["moduleInstances"]["RTL-TCP Source"]["enabled"] = true; defConfig["moduleInstances"]["SDRplay Source"]["module"] = "sdrplay_source"; defConfig["moduleInstances"]["SDRplay Source"]["enabled"] = true; defConfig["moduleInstances"]["SDR++ Server Source"]["module"] = "sdrpp_server_source"; defConfig["moduleInstances"]["SDR++ Server Source"]["enabled"] = true; defConfig["moduleInstances"]["SoapySDR Source"]["module"] = "soapy_source"; defConfig["moduleInstances"]["SoapySDR Source"]["enabled"] = true; defConfig["moduleInstances"]["SpyServer Source"]["module"] = "spyserver_source"; defConfig["moduleInstances"]["SpyServer Source"]["enabled"] = true; defConfig["moduleInstances"]["PlutoSDR Source"]["module"] = "plutosdr_source"; defConfig["moduleInstances"]["PlutoSDR Source"]["enabled"] = true; defConfig["moduleInstances"]["Audio Sink"] = "audio_sink"; defConfig["moduleInstances"]["Network Sink"] = "network_sink"; defConfig["moduleInstances"]["Radio"] = "radio"; defConfig["moduleInstances"]["Frequency Manager"] = "frequency_manager"; defConfig["moduleInstances"]["Recorder"] = "recorder"; defConfig["moduleInstances"]["Rigctl Server"] = "rigctl_server"; // Themes defConfig["theme"] = "Dark"; #ifdef __ANDROID__ defConfig["uiScale"] = 3.0f; #else defConfig["uiScale"] = 1.0f; #endif defConfig["modules"] = json::array(); defConfig["offsetMode"] = (int)0; // Off defConfig["offset"] = 0.0; defConfig["showMenu"] = true; defConfig["showWaterfall"] = true; defConfig["source"] = ""; defConfig["decimationPower"] = 0; defConfig["iqCorrection"] = false; defConfig["streams"]["Radio"]["muted"] = false; defConfig["streams"]["Radio"]["sink"] = "Audio"; defConfig["streams"]["Radio"]["volume"] = 1.0f; defConfig["windowSize"]["h"] = 720; defConfig["windowSize"]["w"] = 1280; defConfig["vfoOffsets"] = json::object(); defConfig["vfoColors"]["Radio"] = "#FFFFFF"; #ifdef __ANDROID__ defConfig["lockMenuOrder"] = true; #else defConfig["lockMenuOrder"] = false; #endif #if defined(_WIN32) defConfig["modulesDirectory"] = "./modules"; defConfig["resourcesDirectory"] = "./res"; #elif defined(IS_MACOS_BUNDLE) defConfig["modulesDirectory"] = "../Plugins"; defConfig["resourcesDirectory"] = "../Resources"; #elif defined(__ANDROID__) defConfig["modulesDirectory"] = root + "/modules"; defConfig["resourcesDirectory"] = root + "/res"; #else defConfig["modulesDirectory"] = INSTALL_PREFIX "/lib/sdrpp/plugins"; defConfig["resourcesDirectory"] = INSTALL_PREFIX "/share/sdrpp"; #endif // Load config spdlog::info("Loading config"); core::configManager.setPath(root + "/config.json"); core::configManager.load(defConfig); core::configManager.enableAutoSave(); core::configManager.acquire(); // Android can't load just any .so file. This means we have to hardcode the name of the modules #ifdef __ANDROID__ int modCount = 0; core::configManager.conf["modules"] = json::array(); core::configManager.conf["modules"][modCount++] = "airspy_source.so"; core::configManager.conf["modules"][modCount++] = "airspyhf_source.so"; core::configManager.conf["modules"][modCount++] = "hackrf_source.so"; core::configManager.conf["modules"][modCount++] = "plutosdr_source.so"; core::configManager.conf["modules"][modCount++] = "sdrpp_server_source.so"; core::configManager.conf["modules"][modCount++] = "rfspace_source.so"; core::configManager.conf["modules"][modCount++] = "rtl_sdr_source.so"; core::configManager.conf["modules"][modCount++] = "rtl_tcp_source.so"; core::configManager.conf["modules"][modCount++] = "spyserver_source.so"; core::configManager.conf["modules"][modCount++] = "network_sink.so"; core::configManager.conf["modules"][modCount++] = "audio_sink.so"; core::configManager.conf["modules"][modCount++] = "m17_decoder.so"; core::configManager.conf["modules"][modCount++] = "meteor_demodulator.so"; core::configManager.conf["modules"][modCount++] = "radio.so"; core::configManager.conf["modules"][modCount++] = "frequency_manager.so"; core::configManager.conf["modules"][modCount++] = "recorder.so"; core::configManager.conf["modules"][modCount++] = "rigctl_server.so"; #endif // Fix missing elements in config for (auto const& item : defConfig.items()) { if (!core::configManager.conf.contains(item.key())) { spdlog::info("Missing key in config {0}, repairing", item.key()); core::configManager.conf[item.key()] = defConfig[item.key()]; } } // Remove unused elements auto items = core::configManager.conf.items(); for (auto const& item : items) { if (!defConfig.contains(item.key())) { spdlog::info("Unused key in config {0}, repairing", item.key()); core::configManager.conf.erase(item.key()); } } // Update to new module representation in config if needed for (auto [_name, inst] : core::configManager.conf["moduleInstances"].items()) { if (!inst.is_string()) { continue; } std::string mod = inst; json newMod; newMod["module"] = mod; newMod["enabled"] = true; core::configManager.conf["moduleInstances"][_name] = newMod; } // Load UI scaling style::uiScale = core::configManager.conf["uiScale"]; core::configManager.release(true); if (serverMode) { return server::main(); } core::configManager.acquire(); std::string resDir = core::configManager.conf["resourcesDirectory"]; json bandColors = core::configManager.conf["bandColors"]; core::configManager.release(); // Assert that the resource directory is absolute and check existence resDir = std::filesystem::absolute(resDir).string(); if (!std::filesystem::is_directory(resDir)) { spdlog::error("Resource directory doesn't exist! Please make sure that you've configured it correctly in config.json (check readme for details)"); return 1; } // Initialize backend int biRes = backend::init(resDir); if (biRes < 0) { return biRes; } // Intialize SmGui in normal mode SmGui::init(false); if (!style::loadFonts(resDir)) { return -1; } thememenu::init(resDir); LoadingScreen::init(); LoadingScreen::show("Loading icons"); spdlog::info("Loading icons"); if (!icons::load(resDir)) { return -1; } LoadingScreen::show("Loading band plans"); spdlog::info("Loading band plans"); bandplan::loadFromDir(resDir + "/bandplans"); LoadingScreen::show("Loading band plan colors"); spdlog::info("Loading band plans color table"); bandplan::loadColorTable(bandColors); gui::mainWindow.init(); spdlog::info("Ready."); // Run render loop (TODO: CHECK RETURN VALUE) backend::renderLoop(); // On android, none of this shutdown should happen due to the way the UI works #ifndef __ANDROID__ // Shut down all modules for (auto& [name, mod] : core::moduleManager.modules) { mod.end(); } // Terminate backend (TODO: CHECK RETURN VALUE) backend::end(); sigpath::iqFrontEnd.stop(); core::configManager.disableAutoSave(); core::configManager.save(); #endif spdlog::info("Exiting successfully"); return 0; }