diff --git a/.gitignore b/.gitignore index 10ad3722..c6b15170 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,6 @@ Folder.DotSettings.user CMakeSettings.json poggers_decoder m17_decoder/libcorrect -SDR++.app \ No newline at end of file +SDR++.app +android/deps +android/app/assets \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b337abb9..a01476e8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,19 @@ else() set(CMAKE_INSTALL_PREFIX "/usr") endif() +# Configure toolchain for android +if (ANDROID) + set(CMAKE_SHARED_LINKER_FLAGS + "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate" + ) + set(CMAKE_C_STANDARD 11) + set(CMAKE_CXX_STANDARD 14) + set(CMAKE_CXX14_EXTENSION_COMPILE_OPTION "-std=c++17") +endif (ANDROID) + # Backends option(OPT_BACKEND_GLFW "Use the GLFW backend" ON) +option(OPT_BACKEND_ANDROID "Use the Android backend" OFF) # Compatibility Options option(OPT_OVERRIDE_STD_FILESYSTEM "Use a local version of std::filesystem on systems that don't have it yet" OFF) @@ -31,6 +42,7 @@ option(OPT_BUILD_SPYSERVER_SOURCE "Build SpyServer Source Module (no dependencie option(OPT_BUILD_PLUTOSDR_SOURCE "Build PlutoSDR Source Module (Dependencies: libiio, libad9361)" ON) # Sinks +option(OPT_BUILD_ANDROID_AUDIO_SINK "Build Android Audio Sink Module (Dependencies: AAudio, only for android)" OFF) option(OPT_BUILD_AUDIO_SINK "Build Audio Sink Module (Dependencies: rtaudio)" ON) option(OPT_BUILD_PORTAUDIO_SINK "Build PortAudio Sink Module (Dependencies: portaudio)" OFF) option(OPT_BUILD_NETWORK_SINK "Build Audio Sink Module (no dependencies required)" ON) @@ -121,6 +133,10 @@ endif (OPT_BUILD_PLUTOSDR_SOURCE) # Sink modules +if (OPT_BUILD_ANDROID_AUDIO_SINK) +add_subdirectory("sink_modules/android_audio_sink") +endif (OPT_BUILD_ANDROID_AUDIO_SINK) + if (OPT_BUILD_AUDIO_SINK) add_subdirectory("sink_modules/audio_sink") endif (OPT_BUILD_AUDIO_SINK) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 13764c47..675cdee8 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -21,6 +21,10 @@ endif () if (OPT_BACKEND_GLFW) file(GLOB_RECURSE BACKEND_SRC "backends/glfw/*.cpp" "backends/glfw/*.c") endif (OPT_BACKEND_GLFW) +if (OPT_BACKEND_ANDROID) + file(GLOB_RECURSE BACKEND_SRC "backends/android/*.cpp" "backends/android/*.c") + set(BACKEND_SRC ${BACKEND_SRC} ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) +endif (OPT_BACKEND_ANDROID) # Add code to dyn lib add_library(sdrpp_core SHARED ${SRC} ${BACKEND_SRC}) @@ -59,6 +63,10 @@ if (OPT_BACKEND_GLFW) target_link_libraries(sdrpp_core PUBLIC ${GLFW3_LIBRARIES}) endif() endif (OPT_BACKEND_GLFW) +if (OPT_BACKEND_ANDROID) + target_include_directories(sdrpp_core PUBLIC "backends/android") + target_include_directories(sdrpp_core PUBLIC "backends/android/imgui") +endif (OPT_BACKEND_ANDROID) # Link to libcorrect if (USE_INTERNAL_LIBCORRECT) @@ -98,7 +106,24 @@ if (MSVC) # ZSTD find_package(zstd CONFIG REQUIRED) target_link_libraries(sdrpp_core PUBLIC zstd::libzstd_shared) +elseif (ANDROID) + target_include_directories(sdrpp_core PUBLIC + ../android/deps/volk/jni + ../android/deps/fftw3/jni + /mnt/android_sdr/zstd/lib + ${ANDROID_NDK}/sources/android/native_app_glue + ) + target_link_libraries(sdrpp_core PUBLIC + ${PROJECT_SOURCE_DIR}/../android/deps/volk/${ANDROID_ABI}/libcpu_features.a + ${PROJECT_SOURCE_DIR}/../android/deps/volk/${ANDROID_ABI}/libvolk.so + ${PROJECT_SOURCE_DIR}/../android/deps/fftw3/${ANDROID_ABI}/libfftw3f.a + /mnt/android_sdr/output/zstd/${ANDROID_ABI}/libzstd.so + android + EGL + GLESv3 + log + ) else() find_package(PkgConfig) find_package(OpenGL REQUIRED) diff --git a/core/backends/glfw/keybinds.h b/core/backends/glfw/keybinds.h index 30dbc6fe..fccc5a73 100644 --- a/core/backends/glfw/keybinds.h +++ b/core/backends/glfw/keybinds.h @@ -21,4 +21,7 @@ #define KB_KEY_LCTRL GLFW_KEY_LEFT_CONTROL #define KB_KEY_RCTRL GLFW_KEY_RIGHT_CONTROL #define KB_KEY_LSHIFT GLFW_KEY_LEFT_SHIFT -#define KB_KEY_RSHIFT GLFW_KEY_RIGHT_SHIFT \ No newline at end of file +#define KB_KEY_RSHIFT GLFW_KEY_RIGHT_SHIFT + +#define KB_KEY_A GLFW_KEY_A +#define KB_KEY_R GLFW_KEY_R \ No newline at end of file diff --git a/core/src/backend.h b/core/src/backend.h index bae9a36c..03f9e3d0 100644 --- a/core/src/backend.h +++ b/core/src/backend.h @@ -2,7 +2,7 @@ #include namespace backend { - int init(std::string resDir); + int init(std::string resDir = ""); void beginFrame(); void render(bool vsync = true); void getMouseScreenPos(double& x, double& y); diff --git a/core/src/core.cpp b/core/src/core.cpp index 4d6d8039..042ad089 100644 --- a/core/src/core.cpp +++ b/core/src/core.cpp @@ -1,7 +1,5 @@ #include #include "imgui.h" -#include "imgui_impl_glfw.h" -#include "imgui_impl_opengl3.h" #include #include #include @@ -188,6 +186,7 @@ int sdrpp_main(int argc, char* argv[]) { defConfig["theme"] = "Dark"; defConfig["modules"] = json::array(); + defConfig["offsetMode"] = (int)0; // Off defConfig["offset"] = 0.0; defConfig["showMenu"] = true; @@ -213,6 +212,9 @@ int sdrpp_main(int argc, char* argv[]) { #elif defined(IS_MACOS_BUNDLE) defConfig["modulesDirectory"] = "../Plugins"; defConfig["resourcesDirectory"] = "../Resources"; +#elif defined(__ANDROID__) + defConfig["modulesDirectory"] = options::opts.root + "/modules"; + defConfig["resourcesDirectory"] = options::opts.root + "/res"; #else defConfig["modulesDirectory"] = INSTALL_PREFIX "/lib/sdrpp/plugins"; defConfig["resourcesDirectory"] = INSTALL_PREFIX "/share/sdrpp"; @@ -223,8 +225,33 @@ int sdrpp_main(int argc, char* argv[]) { core::configManager.setPath(options::opts.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++] = "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())) { @@ -274,6 +301,7 @@ int sdrpp_main(int argc, char* argv[]) { if (!style::loadFonts(resDir)) { return -1; } thememenu::init(resDir); + LoadingScreen::init(); LoadingScreen::show("Loading icons"); spdlog::info("Loading icons"); @@ -294,6 +322,8 @@ int sdrpp_main(int argc, char* argv[]) { // 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(); @@ -306,6 +336,7 @@ int sdrpp_main(int argc, char* argv[]) { core::configManager.disableAutoSave(); core::configManager.save(); +#endif return 0; } diff --git a/core/src/gui/dialogs/credits.cpp b/core/src/gui/dialogs/credits.cpp index b54ad315..781677b6 100644 --- a/core/src/gui/dialogs/credits.cpp +++ b/core/src/gui/dialogs/credits.cpp @@ -8,8 +8,10 @@ namespace credits { ImFont* bigFont; + ImVec2 imageSize(128.0f, 128.0f); void init() { + imageSize = ImVec2(128.0f * style::uiScale, 128.0f * style::uiScale); } void show() { @@ -25,7 +27,7 @@ namespace credits { ImGui::TextUnformatted("SDR++ "); ImGui::PopFont(); ImGui::SameLine(); - ImGui::Image(icons::LOGO, ImVec2(128, 128)); + ImGui::Image(icons::LOGO, imageSize); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); diff --git a/core/src/gui/dialogs/loading_screen.cpp b/core/src/gui/dialogs/loading_screen.cpp index 65efa3c4..afeffc48 100644 --- a/core/src/gui/dialogs/loading_screen.cpp +++ b/core/src/gui/dialogs/loading_screen.cpp @@ -1,8 +1,6 @@ #include #include #include -#include "imgui_impl_glfw.h" -#include "imgui_impl_opengl3.h" #include #include #include @@ -10,6 +8,11 @@ #include namespace LoadingScreen { + ImVec2 imageSize(128.0f, 128.0f); + + void init() { + imageSize = ImVec2(128.0f * style::uiScale, 128.0f * style::uiScale); + } void show(std::string msg) { backend::beginFrame(); @@ -26,7 +29,7 @@ namespace LoadingScreen { ImGui::TextUnformatted("SDR++ "); ImGui::PopFont(); ImGui::SameLine(); - ImGui::Image(icons::LOGO, ImVec2(128, 128)); + ImGui::Image(icons::LOGO, imageSize); ImVec2 origPos = ImGui::GetCursorPos(); ImGui::SetCursorPosY(origPos.y + 50); diff --git a/core/src/gui/dialogs/loading_screen.h b/core/src/gui/dialogs/loading_screen.h index 7ed85ccf..4b016cd8 100644 --- a/core/src/gui/dialogs/loading_screen.h +++ b/core/src/gui/dialogs/loading_screen.h @@ -4,5 +4,6 @@ #include namespace LoadingScreen { + void init(); void show(std::string msg); }; \ No newline at end of file diff --git a/core/src/gui/main_window.cpp b/core/src/gui/main_window.cpp index 994fa6a2..3ca1b7fc 100644 --- a/core/src/gui/main_window.cpp +++ b/core/src/gui/main_window.cpp @@ -1,8 +1,6 @@ #include #include #include "imgui.h" -#include "imgui_impl_glfw.h" -#include "imgui_impl_opengl3.h" #include #include #include @@ -126,10 +124,14 @@ void MainWindow::init() { // Load additional modules specified through config for (auto const& path : modules) { +#ifndef __ANDROID__ std::string apath = std::filesystem::absolute(path).string(); spdlog::info("Loading {0}", apath); LoadingScreen::show("Loading " + std::filesystem::path(path).filename().string()); core::moduleManager.loadModule(apath); +#else + core::moduleManager.loadModule(path); +#endif } // Create module instances @@ -354,8 +356,9 @@ void MainWindow::draw() { } // To Bar + ImVec2 btnSize(30 * style::uiScale, 30 * style::uiScale); ImGui::PushID(ImGui::GetID("sdrpp_menu_btn")); - if (ImGui::ImageButton(icons::MENU, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(KB_KEY_MENU, false)) { + if (ImGui::ImageButton(icons::MENU, btnSize, ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(KB_KEY_MENU, false)) { showMenu = !showMenu; core::configManager.acquire(); core::configManager.conf["showMenu"] = showMenu; @@ -369,14 +372,14 @@ void MainWindow::draw() { if (playButtonLocked && !tmpPlaySate) { style::beginDisabled(); } if (playing) { ImGui::PushID(ImGui::GetID("sdrpp_stop_btn")); - if (ImGui::ImageButton(icons::STOP, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(KB_KEY_END, false)) { + if (ImGui::ImageButton(icons::STOP, btnSize, ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(KB_KEY_END, false)) { setPlayState(false); } ImGui::PopID(); } else { // TODO: Might need to check if there even is a device ImGui::PushID(ImGui::GetID("sdrpp_play_btn")); - if (ImGui::ImageButton(icons::PLAY, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(KB_KEY_END, false)) { + if (ImGui::ImageButton(icons::PLAY, btnSize, ImVec2(0, 0), ImVec2(1, 1), 5) || ImGui::IsKeyPressed(KB_KEY_END, false)) { setPlayState(true); } ImGui::PopID(); @@ -384,20 +387,21 @@ void MainWindow::draw() { if (playButtonLocked && !tmpPlaySate) { style::endDisabled(); } ImGui::SameLine(); + float origY = ImGui::GetCursorPosY(); - //ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 8); - sigpath::sinkManager.showVolumeSlider(gui::waterfall.selectedVFO, "##_sdrpp_main_volume_", 248, 30, 5, true); + sigpath::sinkManager.showVolumeSlider(gui::waterfall.selectedVFO, "##_sdrpp_main_volume_", 248 * style::uiScale, btnSize.x, 5, true); ImGui::SameLine(); + ImGui::SetCursorPosY(origY); gui::freqSelect.draw(); ImGui::SameLine(); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() - 9); + ImGui::SetCursorPosY(origY); if (tuningMode == tuner::TUNER_MODE_CENTER) { ImGui::PushID(ImGui::GetID("sdrpp_ena_st_btn")); - if (ImGui::ImageButton(icons::CENTER_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) { + if (ImGui::ImageButton(icons::CENTER_TUNING, btnSize, ImVec2(0, 0), ImVec2(1, 1), 5)) { tuningMode = tuner::TUNER_MODE_NORMAL; gui::waterfall.VFOMoveSingleClick = false; core::configManager.acquire(); @@ -408,7 +412,7 @@ void MainWindow::draw() { } else { // TODO: Might need to check if there even is a device ImGui::PushID(ImGui::GetID("sdrpp_dis_st_btn")); - if (ImGui::ImageButton(icons::NORMAL_TUNING, ImVec2(30, 30), ImVec2(0, 0), ImVec2(1, 1), 5)) { + if (ImGui::ImageButton(icons::NORMAL_TUNING, btnSize, ImVec2(0, 0), ImVec2(1, 1), 5)) { tuningMode = tuner::TUNER_MODE_CENTER; gui::waterfall.VFOMoveSingleClick = true; tuner::tune(tuner::TUNER_MODE_CENTER, gui::waterfall.selectedVFO, gui::freqSelect.frequency); @@ -421,19 +425,20 @@ void MainWindow::draw() { ImGui::SameLine(); - int snrWidth = std::min(300, ImGui::GetWindowSize().x - ImGui::GetCursorPosX() - 87); + int snrOffset = 87.0f * style::uiScale; + int snrWidth = std::min(300.0f * style::uiScale, ImGui::GetWindowSize().x - ImGui::GetCursorPosX() - snrOffset); - ImGui::SetCursorPosX(ImGui::GetWindowSize().x - (snrWidth + 87)); - ImGui::SetCursorPosY(ImGui::GetCursorPosY() + 5); + ImGui::SetCursorPosX(ImGui::GetWindowSize().x - (snrWidth + snrOffset)); + ImGui::SetCursorPosY(origY + (5.0f * style::uiScale)); ImGui::SetNextItemWidth(snrWidth); ImGui::SNRMeter((vfo != NULL) ? gui::waterfall.selectedVFOSNR : 0); ImGui::SameLine(); // Logo button - ImGui::SetCursorPosX(ImGui::GetWindowSize().x - 48); - ImGui::SetCursorPosY(10); - if (ImGui::ImageButton(icons::LOGO, ImVec2(32, 32), ImVec2(0, 0), ImVec2(1, 1), 0)) { + ImGui::SetCursorPosX(ImGui::GetWindowSize().x - (48 * style::uiScale)); + ImGui::SetCursorPosY(10 * style::uiScale); + if (ImGui::ImageButton(icons::LOGO, ImVec2(32 * style::uiScale, 32 * style::uiScale), ImVec2(0, 0), ImVec2(1, 1), 0)) { showCredits = true; } if (ImGui::IsMouseDown(ImGuiMouseButton_Left)) { @@ -455,7 +460,7 @@ void MainWindow::draw() { newWidth = std::clamp(newWidth, 250, winSize.x - 250); ImGui::GetForegroundDrawList()->AddLine(ImVec2(newWidth, curY), ImVec2(newWidth, winSize.y - 10), ImGui::GetColorU32(ImGuiCol_SeparatorActive)); } - if (mousePos.x >= newWidth - 2 && mousePos.x <= newWidth + 2 && mousePos.y > curY) { + if (mousePos.x >= newWidth - (2.0f * style::uiScale) && mousePos.x <= newWidth + (2.0f * style::uiScale) && mousePos.y > curY) { ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); if (click) { grabbingMenu = true; @@ -479,8 +484,8 @@ void MainWindow::draw() { if (showMenu) { ImGui::Columns(3, "WindowColumns", false); ImGui::SetColumnWidth(0, menuWidth); - ImGui::SetColumnWidth(1, winSize.x - menuWidth - 60); - ImGui::SetColumnWidth(2, 60); + ImGui::SetColumnWidth(1, winSize.x - menuWidth - (60.0f * style::uiScale)); + ImGui::SetColumnWidth(2, 60.0f * style::uiScale); ImGui::BeginChild("Left Column"); if (gui::menu.draw(firstMenuRender)) { @@ -513,7 +518,7 @@ void MainWindow::draw() { ImGui::Text("Center Frequency: %.0f Hz", gui::waterfall.getCenterFrequency()); ImGui::Text("Source name: %s", sourceName.c_str()); ImGui::Checkbox("Show demo window", &demoWindow); - ImGui::Text("ImGui version: %s", ImGui::GetVersion()); + ImGui::Text("ImGui version: %s_feb_2022", ImGui::GetVersion()); ImGui::Checkbox("Bypass buffering", &sigpath::signalPath.inputBuffer.bypass); @@ -538,9 +543,9 @@ void MainWindow::draw() { else { // When hiding the menu bar ImGui::Columns(3, "WindowColumns", false); - ImGui::SetColumnWidth(0, 8); - ImGui::SetColumnWidth(1, winSize.x - 8 - 60); - ImGui::SetColumnWidth(2, 60); + ImGui::SetColumnWidth(0, 8 * style::uiScale); + ImGui::SetColumnWidth(1, winSize.x - ((8 + 60) * style::uiScale)); + ImGui::SetColumnWidth(2, 60.0f * style::uiScale); } // Right Column @@ -602,8 +607,9 @@ void MainWindow::draw() { ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Zoom").x / 2.0)); ImGui::TextUnformatted("Zoom"); - ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10); - if (ImGui::VSliderFloat("##_7_", ImVec2(20.0, 150.0), &bw, 1.0, 0.0, "")) { + ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10 * style::uiScale); + ImVec2 wfSliderSize(20.0 * style::uiScale, 150.0 * style::uiScale); + if (ImGui::VSliderFloat("##_7_", wfSliderSize, &bw, 1.0, 0.0, "")) { double factor = (double)bw * (double)bw; // Map 0.0 -> 1.0 to 1000.0 -> bandwidth @@ -621,8 +627,8 @@ void MainWindow::draw() { ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Max").x / 2.0)); ImGui::TextUnformatted("Max"); - ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10); - if (ImGui::VSliderFloat("##_8_", ImVec2(20.0, 150.0), &fftMax, 0.0, -160.0f, "")) { + ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10 * style::uiScale); + if (ImGui::VSliderFloat("##_8_", wfSliderSize, &fftMax, 0.0, -160.0f, "")) { fftMax = std::max(fftMax, fftMin + 10); core::configManager.acquire(); core::configManager.conf["max"] = fftMax; @@ -633,8 +639,8 @@ void MainWindow::draw() { ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - (ImGui::CalcTextSize("Min").x / 2.0)); ImGui::TextUnformatted("Min"); - ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10); - if (ImGui::VSliderFloat("##_9_", ImVec2(20.0, 150.0), &fftMin, 0.0, -160.0f, "")) { + ImGui::SetCursorPosX((ImGui::GetWindowSize().x / 2.0) - 10 * style::uiScale); + if (ImGui::VSliderFloat("##_9_", wfSliderSize, &fftMin, 0.0, -160.0f, "")) { fftMin = std::min(fftMax - 10, fftMin); core::configManager.acquire(); core::configManager.conf["min"] = fftMin; @@ -707,4 +713,8 @@ void MainWindow::setFFTWindow(int win) { bool MainWindow::isPlaying() { return playing; +} + +void MainWindow::setFirstMenuRender() { + firstMenuRender = true; } \ No newline at end of file diff --git a/core/src/gui/main_window.h b/core/src/gui/main_window.h index 51cc5fcd..b46cff1f 100644 --- a/core/src/gui/main_window.h +++ b/core/src/gui/main_window.h @@ -19,6 +19,7 @@ public: bool sdrIsRunning(); void setFFTSize(int size); void setFFTWindow(int win); + void setFirstMenuRender(); // TODO: Replace with it's own class void setVFO(double freq); diff --git a/core/src/gui/menus/theme.cpp b/core/src/gui/menus/theme.cpp index bdfb1d6f..45a40b7c 100644 --- a/core/src/gui/menus/theme.cpp +++ b/core/src/gui/menus/theme.cpp @@ -23,8 +23,11 @@ namespace thememenu { it = std::find(themeNames.begin(), themeNames.end(), "Dark"); selectedThemeName = "Dark"; } - gui::themeManager.applyTheme(selectedThemeName); themeId = std::distance(themeNames.begin(), it); + applyTheme(); + + // Apply scaling + ImGui::GetStyle().ScaleAllSizes(style::uiScale); themeNamesTxt = ""; for (auto name : themeNames) { @@ -33,12 +36,16 @@ namespace thememenu { } } + void applyTheme() { + gui::themeManager.applyTheme(themeNames[themeId]); + } + void draw(void* ctx) { float menuWidth = ImGui::GetContentRegionAvailWidth(); ImGui::LeftLabel("Theme"); ImGui::SetNextItemWidth(menuWidth - ImGui::GetCursorPosX()); if (ImGui::Combo("##theme_select_combo", &themeId, themeNamesTxt.c_str())) { - gui::themeManager.applyTheme(themeNames[themeId]); + applyTheme(); core::configManager.acquire(); core::configManager.conf["theme"] = themeNames[themeId]; core::configManager.release(true); diff --git a/core/src/gui/menus/theme.h b/core/src/gui/menus/theme.h index b41357cf..5320b044 100644 --- a/core/src/gui/menus/theme.h +++ b/core/src/gui/menus/theme.h @@ -3,5 +3,6 @@ namespace thememenu { void init(std::string resDir); + void applyTheme(); void draw(void* ctx); } \ No newline at end of file diff --git a/core/src/gui/style.cpp b/core/src/gui/style.cpp index 1db145cc..75da7a18 100644 --- a/core/src/gui/style.cpp +++ b/core/src/gui/style.cpp @@ -11,15 +11,21 @@ namespace style { ImFont* bigFont; ImFont* hugeFont; +#ifndef __ANDROID__ + float uiScale = 1.0f; +#else + float uiScale = 3.0f; +#endif + bool loadFonts(std::string resDir) { if (!std::filesystem::is_directory(resDir)) { spdlog::error("Invalid resource directory: {0}", resDir); return false; } - baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 16.0f); - bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 45.0f); - hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 128.0f); + baseFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 16.0f * uiScale); + bigFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 45.0f * uiScale); + hugeFont = ImGui::GetIO().Fonts->AddFontFromFileTTF(((std::string)(resDir + "/fonts/Roboto-Medium.ttf")).c_str(), 128.0f * uiScale); return true; } diff --git a/core/src/gui/style.h b/core/src/gui/style.h index d1bda7c0..5f75b8e0 100644 --- a/core/src/gui/style.h +++ b/core/src/gui/style.h @@ -7,6 +7,8 @@ namespace style { extern ImFont* bigFont; extern ImFont* hugeFont; + extern float uiScale; + bool setDefaultStyle(std::string resDir); bool loadFonts(std::string resDir); void beginDisabled(); diff --git a/core/src/gui/widgets/frequency_select.cpp b/core/src/gui/widgets/frequency_select.cpp index 90d7968e..a885e3f3 100644 --- a/core/src/gui/widgets/frequency_select.cpp +++ b/core/src/gui/widgets/frequency_select.cpp @@ -42,9 +42,6 @@ void FrequencySelect::onPosChange() { } } -void FrequencySelect::onResize() { -} - void FrequencySelect::incrementDigit(int i) { if (i < 0) { return; @@ -93,34 +90,27 @@ void FrequencySelect::moveCursorToDigit(int i) { } void FrequencySelect::draw() { - window = ImGui::GetCurrentWindow(); + auto window = ImGui::GetCurrentWindow(); widgetPos = ImGui::GetWindowContentRegionMin(); - widgetEndPos = ImGui::GetWindowContentRegionMax(); ImVec2 cursorPos = ImGui::GetCursorPos(); widgetPos.x += window->Pos.x + cursorPos.x; - widgetPos.y += window->Pos.y - 3; - widgetEndPos.x += window->Pos.x + cursorPos.x; - widgetEndPos.y += window->Pos.y - 3; - widgetSize = ImVec2(widgetEndPos.x - widgetPos.x, widgetEndPos.y - widgetPos.y); - ImGui::PushFont(style::bigFont); + ImVec2 digitSz = ImGui::CalcTextSize("0"); + ImVec2 commaSz = ImGui::CalcTextSize("."); + widgetPos.y = cursorPos.y - ((digitSz.y / 2.0f) - ceilf(15 * style::uiScale) - 5); if (widgetPos.x != lastWidgetPos.x || widgetPos.y != lastWidgetPos.y) { lastWidgetPos = widgetPos; onPosChange(); } - if (widgetSize.x != lastWidgetSize.x || widgetSize.y != lastWidgetSize.y) { - lastWidgetSize = widgetSize; - onResize(); - } ImU32 disabledColor = ImGui::GetColorU32(ImGuiCol_Text, 0.3f); ImU32 textColor = ImGui::GetColorU32(ImGuiCol_Text); - ImVec2 digitSz = ImGui::CalcTextSize("0"); - ImVec2 commaSz = ImGui::CalcTextSize("."); + int digitWidth = digitSz.x; int commaOffset = 0; + float textOffset = 11.0f * style::uiScale; bool zeros = true; ImGui::ItemSize(ImRect(digitTopMins[0], ImVec2(digitBottomMaxs[11].x + 15, digitBottomMaxs[11].y))); @@ -134,7 +124,7 @@ void FrequencySelect::draw() { zeros ? disabledColor : textColor, buf); if ((i + 1) % 3 == 0 && i < 11) { commaOffset += commaSz.x; - window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + 11, widgetPos.y), + window->DrawList->AddText(ImVec2(widgetPos.x + (i * digitWidth) + commaOffset + textOffset, widgetPos.y), zeros ? disabledColor : textColor, "."); } } @@ -223,9 +213,7 @@ void FrequencySelect::draw() { ImGui::PopFont(); - ImGui::SetCursorPosX(digitBottomMaxs[11].x + 17); - - //ImGui::NewLine(); + ImGui::SetCursorPosX(digitBottomMaxs[11].x + (17.0f * style::uiScale)); } void FrequencySelect::setFrequency(int64_t freq) { diff --git a/core/src/gui/widgets/frequency_select.h b/core/src/gui/widgets/frequency_select.h index 7f53965d..a5860d88 100644 --- a/core/src/gui/widgets/frequency_select.h +++ b/core/src/gui/widgets/frequency_select.h @@ -26,13 +26,7 @@ private: void moveCursorToDigit(int i); ImVec2 widgetPos; - ImVec2 widgetEndPos; - ImVec2 widgetSize; - ImVec2 lastWidgetPos; - ImVec2 lastWidgetSize; - - ImGuiWindow* window; int digits[12]; ImVec2 digitBottomMins[12]; diff --git a/core/src/gui/widgets/menu.cpp b/core/src/gui/widgets/menu.cpp index df8eaee4..384ad5d3 100644 --- a/core/src/gui/widgets/menu.cpp +++ b/core/src/gui/widgets/menu.cpp @@ -28,6 +28,7 @@ bool Menu::draw(bool updateStates) { bool changed = false; float menuWidth = ImGui::GetContentRegionAvailWidth(); ImGuiWindow* window = ImGui::GetCurrentWindow(); + ImVec2 checkboxOffset = ImVec2(menuWidth - ImGui::GetTextLineHeight() - (6.0f * style::uiScale), - ImGui::GetTextLineHeight() - (10.0f * style::uiScale)); int displayedCount = 0; int rawId = 0; @@ -46,6 +47,7 @@ bool Menu::draw(bool updateStates) { continue; } + // Draw dragged menu item if (displayedCount == insertBefore && !draggedMenuName.empty()) { if (updateStates) { ImGui::SetNextItemOpen(false); } ImVec2 posMin = ImGui::GetCursorScreenPos(); @@ -56,14 +58,14 @@ bool Menu::draw(bool updateStates) { if (items[draggedOpt.name].inst != NULL) { window->WorkRect = orignalRect; ImVec2 pos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6); - ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight()); + ImGui::SetCursorPosX(pos.x + checkboxOffset.x); + ImGui::SetCursorPosY(pos.y + checkboxOffset.y); bool enabled = items[draggedOpt.name].inst->isEnabled(); ImGui::Checkbox(("##_menu_checkbox_" + draggedOpt.name).c_str(), &enabled); ImGui::SetCursorPos(pos); } style::endDisabled(); - window->DrawList->AddRect(posMin, posMax, textColor); + window->DrawList->AddRect(posMin, posMax, textColor, 0.0f, 0, style::uiScale); } displayedCount++; @@ -72,7 +74,7 @@ bool Menu::draw(bool updateStates) { ImRect orginalRect = window->WorkRect; if (item.inst != NULL) { - window->WorkRect = ImRect(orginalRect.Min, ImVec2(orginalRect.Max.x - ImGui::GetTextLineHeight() - 6, orginalRect.Max.y)); + window->WorkRect = ImRect(orginalRect.Min, ImVec2(orginalRect.Max.x - ImGui::GetTextLineHeight() - (6.0f * style::uiScale), orginalRect.Max.y)); } ImVec2 posMin = ImGui::GetCursorScreenPos(); @@ -93,13 +95,14 @@ bool Menu::draw(bool updateStates) { continue; } + // Draw menu header and menu content. There is a lot of boilerplate because the checkbox has to be drawn before the menu, TODO: fix if (updateStates) { ImGui::SetNextItemOpen(opt.open); } if (ImGui::CollapsingHeader((opt.name + "##sdrpp_main_menu").c_str())) { if (item.inst != NULL) { window->WorkRect = orginalRect; ImVec2 pos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6); - ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight()); + ImGui::SetCursorPosX(pos.x + checkboxOffset.x); + ImGui::SetCursorPosY(pos.y + checkboxOffset.y); bool enabled = item.inst->isEnabled(); if (ImGui::Checkbox(("##_menu_checkbox_" + opt.name).c_str(), &enabled)) { enabled ? item.inst->enable() : item.inst->disable(); @@ -120,8 +123,8 @@ bool Menu::draw(bool updateStates) { else if (item.inst != NULL) { window->WorkRect = orginalRect; ImVec2 pos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6); - ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight()); + ImGui::SetCursorPosX(pos.x + checkboxOffset.x); + ImGui::SetCursorPosY(pos.y + checkboxOffset.y); bool enabled = item.inst->isEnabled(); if (ImGui::Checkbox(("##_menu_checkbox_" + opt.name).c_str(), &enabled)) { enabled ? item.inst->enable() : item.inst->disable(); @@ -168,7 +171,7 @@ bool Menu::draw(bool updateStates) { insertBefore = -1; } - + // TODO: Figure out why the hell this is needed if (insertBefore == displayedCount && !draggedMenuName.empty()) { if (updateStates) { ImGui::SetNextItemOpen(false); } ImVec2 posMin = ImGui::GetCursorScreenPos(); @@ -179,14 +182,14 @@ bool Menu::draw(bool updateStates) { if (items[draggedOpt.name].inst != NULL) { window->WorkRect = orignalRect; ImVec2 pos = ImGui::GetCursorPos(); - ImGui::SetCursorPosX(pos.x + menuWidth - ImGui::GetTextLineHeight() - 6); - ImGui::SetCursorPosY(pos.y - 10 - ImGui::GetTextLineHeight()); + ImGui::SetCursorPosX(pos.x + checkboxOffset.x); + ImGui::SetCursorPosY(pos.y + checkboxOffset.y); bool enabled = items[draggedOpt.name].inst->isEnabled(); ImGui::Checkbox(("##_menu_checkbox_" + draggedOpt.name).c_str(), &enabled); ImGui::SetCursorPos(pos); } style::endDisabled(); - window->DrawList->AddRect(posMin, posMax, textColor); + window->DrawList->AddRect(posMin, posMax, textColor, 0.0f, 0, style::uiScale); } if (!draggedMenuName.empty()) { diff --git a/core/src/gui/widgets/snr_meter.cpp b/core/src/gui/widgets/snr_meter.cpp index 4941ee42..29a4906a 100644 --- a/core/src/gui/widgets/snr_meter.cpp +++ b/core/src/gui/widgets/snr_meter.cpp @@ -1,5 +1,6 @@ #include #include +#include #ifndef IMGUI_DEFINE_MATH_OPERATORS #define IMGUI_DEFINE_MATH_OPERATORS @@ -27,15 +28,15 @@ namespace ImGui { float it = size.x / 9; char buf[32]; - window->DrawList->AddRectFilled(min + ImVec2(0, 1), min + ImVec2(roundf((float)val * ratio), 10), IM_COL32(0, 136, 255, 255)); - window->DrawList->AddLine(min, min + ImVec2(0, 9), text); - window->DrawList->AddLine(min + ImVec2(0, 9), min + ImVec2(size.x + 1, 9), text); + window->DrawList->AddRectFilled(min + ImVec2(0, 1), min + ImVec2(roundf((float)val * ratio), 10 * style::uiScale), IM_COL32(0, 136, 255, 255)); + window->DrawList->AddLine(min, min + ImVec2(0, (10.0f * style::uiScale) - 1), text, style::uiScale); + window->DrawList->AddLine(min + ImVec2(0, (10.0f * style::uiScale) - 1), min + ImVec2(size.x + 1, (10.0f * style::uiScale) - 1), text, style::uiScale); for (int i = 0; i < 10; i++) { - window->DrawList->AddLine(min + ImVec2(roundf((float)i * it), 9), min + ImVec2(roundf((float)i * it), 14), text); + window->DrawList->AddLine(min + ImVec2(roundf((float)i * it), (10.0f * style::uiScale) - 1), min + ImVec2(roundf((float)i * it), (15.0f * style::uiScale) - 1), text, style::uiScale); sprintf(buf, "%d", i * 10); ImVec2 sz = ImGui::CalcTextSize(buf); - window->DrawList->AddText(min + ImVec2(roundf(((float)i * it) - (sz.x / 2.0)) + 1, 16), text, buf); + window->DrawList->AddText(min + ImVec2(roundf(((float)i * it) - (sz.x / 2.0)) + 1, 16.0f * style::uiScale), text, buf); } } } \ No newline at end of file diff --git a/core/src/gui/widgets/waterfall.cpp b/core/src/gui/widgets/waterfall.cpp index d2f598ef..0dae66d6 100644 --- a/core/src/gui/widgets/waterfall.cpp +++ b/core/src/gui/widgets/waterfall.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include float DEFAULT_COLOR_MAP[][3] = { @@ -99,51 +100,53 @@ namespace ImGui { ImU32 trace = ImGui::GetColorU32(ImGuiCol_PlotLines); ImU32 shadow = ImGui::GetColorU32(ImGuiCol_PlotLines, 0.2); ImU32 text = ImGui::GetColorU32(ImGuiCol_Text); + float textVOffset = 10.0f * style::uiScale; // Vertical scale for (float line = startLine; line > fftMin; line -= vRange) { - float yPos = widgetPos.y + fftHeight + 10 - ((line - fftMin) * scaleFactor); - window->DrawList->AddLine(ImVec2(roundf(widgetPos.x + 50), roundf(yPos)), - ImVec2(roundf(widgetPos.x + dataWidth + 50), roundf(yPos)), - IM_COL32(50, 50, 50, 255), 1.0); + float yPos = fftAreaMax.y - ((line - fftMin) * scaleFactor); + window->DrawList->AddLine(ImVec2(fftAreaMin.x, roundf(yPos)), + ImVec2(fftAreaMax.x, roundf(yPos)), + IM_COL32(50, 50, 50, 255), style::uiScale); sprintf(buf, "%d", (int)line); ImVec2 txtSz = ImGui::CalcTextSize(buf); - window->DrawList->AddText(ImVec2(widgetPos.x + 40 - txtSz.x, roundf(yPos - (txtSz.y / 2.0))), text, buf); + window->DrawList->AddText(ImVec2(fftAreaMin.x - txtSz.x - textVOffset, roundf(yPos - (txtSz.y / 2.0))), text, buf); } // Horizontal scale double startFreq = ceilf(lowerFreq / range) * range; double horizScale = (double)dataWidth / viewBandwidth; + float scaleVOfsset = 7 * style::uiScale; for (double freq = startFreq; freq < upperFreq; freq += range) { - double xPos = widgetPos.x + 50 + ((freq - lowerFreq) * horizScale); - window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + 10), - ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10), - IM_COL32(50, 50, 50, 255), 1.0); - window->DrawList->AddLine(ImVec2(roundf(xPos), widgetPos.y + fftHeight + 10), - ImVec2(roundf(xPos), widgetPos.y + fftHeight + 17), - text, 1.0); + double xPos = fftAreaMin.x + ((freq - lowerFreq) * horizScale); + window->DrawList->AddLine(ImVec2(roundf(xPos), fftAreaMin.y + 1), + ImVec2(roundf(xPos), fftAreaMax.y), + IM_COL32(50, 50, 50, 255), style::uiScale); + window->DrawList->AddLine(ImVec2(roundf(xPos), fftAreaMax.y), + ImVec2(roundf(xPos), fftAreaMax.y + scaleVOfsset), + text, style::uiScale); printAndScale(freq, buf); ImVec2 txtSz = ImGui::CalcTextSize(buf); - window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0)), widgetPos.y + fftHeight + 10 + txtSz.y), text, buf); + window->DrawList->AddText(ImVec2(roundf(xPos - (txtSz.x / 2.0)), fftAreaMax.y + txtSz.y), text, buf); } // Data if (latestFFT != NULL && fftLines != 0) { for (int i = 1; i < dataWidth; i++) { - double aPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i - 1] - fftMin) * scaleFactor); - double bPos = widgetPos.y + fftHeight + 10 - ((latestFFT[i] - fftMin) * scaleFactor); - aPos = std::clamp(aPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10); - bPos = std::clamp(bPos, widgetPos.y + 10, widgetPos.y + fftHeight + 10); - window->DrawList->AddLine(ImVec2(widgetPos.x + 49 + i, roundf(aPos)), - ImVec2(widgetPos.x + 50 + i, roundf(bPos)), trace, 1.0); - window->DrawList->AddLine(ImVec2(widgetPos.x + 50 + i, roundf(bPos)), - ImVec2(widgetPos.x + 50 + i, widgetPos.y + fftHeight + 10), shadow, 1.0); + double aPos = fftAreaMax.y - ((latestFFT[i - 1] - fftMin) * scaleFactor); + double bPos = fftAreaMax.y - ((latestFFT[i] - fftMin) * scaleFactor); + aPos = std::clamp(aPos, fftAreaMin.y + 1, fftAreaMax.y); + bPos = std::clamp(bPos, fftAreaMin.y + 1, fftAreaMax.y); + window->DrawList->AddLine(ImVec2(fftAreaMin.x + i - 1, roundf(aPos)), + ImVec2(fftAreaMin.x + i, roundf(bPos)), trace, 1.0); + window->DrawList->AddLine(ImVec2(fftAreaMin.x + i, roundf(bPos)), + ImVec2(fftAreaMin.x + i, fftAreaMax.y), shadow, 1.0); } } FFTRedrawArgs args; - args.min = ImVec2(widgetPos.x + 50, widgetPos.y + 9); - args.max = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10); + args.min = fftAreaMin; + args.max = fftAreaMax; args.lowFreq = lowerFreq; args.highFreq = upperFreq; args.freqToPixelRatio = horizScale; @@ -152,13 +155,13 @@ namespace ImGui { onFFTRedraw.emit(args); // X Axis - window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 10), - ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10), - text, 1.0); + window->DrawList->AddLine(ImVec2(fftAreaMin.x, fftAreaMax.y), + ImVec2(fftAreaMax.x, fftAreaMax.y), + text, style::uiScale); // Y Axis - window->DrawList->AddLine(ImVec2(widgetPos.x + 50, widgetPos.y + 9), - ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 9), - text, 1.0); + window->DrawList->AddLine(ImVec2(fftAreaMin.x, fftAreaMin.y), + ImVec2(fftAreaMin.x, fftAreaMax.y - 1), + text, style::uiScale); } void WaterFall::drawWaterfall() { @@ -173,7 +176,7 @@ namespace ImGui { for (auto const& [name, vfo] : vfos) { window->DrawList->AddRectFilled(vfo->wfRectMin, vfo->wfRectMax, vfo->color); if (!vfo->lineVisible) { continue; } - window->DrawList->AddLine(vfo->wfLineMin, vfo->wfLineMax, (name == selectedVFO) ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255)); + window->DrawList->AddLine(vfo->wfLineMin, vfo->wfLineMax, (name == selectedVFO) ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255), style::uiScale); } } } @@ -212,7 +215,7 @@ namespace ImGui { bool mouseClicked = ImGui::ButtonBehavior(ImRect(fftAreaMin, wfMax), GetID("WaterfallID"), &mouseHovered, &mouseHeld, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_PressedOnClick); - mouseInFFTResize = (dragOrigin.x > widgetPos.x && dragOrigin.x < widgetPos.x + widgetSize.x && dragOrigin.y >= widgetPos.y + newFFTAreaHeight - 2 && dragOrigin.y <= widgetPos.y + newFFTAreaHeight + 2); + mouseInFFTResize = (dragOrigin.x > widgetPos.x && dragOrigin.x < widgetPos.x + widgetSize.x && dragOrigin.y >= widgetPos.y + newFFTAreaHeight - (2.0f * style::uiScale) && dragOrigin.y <= widgetPos.y + newFFTAreaHeight + (2.0f * style::uiScale)); mouseInFreq = IS_IN_AREA(dragOrigin, freqAreaMin, freqAreaMax); mouseInFFT = IS_IN_AREA(dragOrigin, fftAreaMin, fftAreaMax); mouseInWaterfall = IS_IN_AREA(dragOrigin, wfMin, wfMax); @@ -304,7 +307,7 @@ namespace ImGui { newFFTAreaHeight = mousePos.y - widgetPos.y; newFFTAreaHeight = std::clamp(newFFTAreaHeight, 150, widgetSize.y - 50); ImGui::GetForegroundDrawList()->AddLine(ImVec2(widgetPos.x, newFFTAreaHeight + widgetPos.y), ImVec2(widgetEndPos.x, newFFTAreaHeight + widgetPos.y), - ImGui::GetColorU32(ImGuiCol_SeparatorActive)); + ImGui::GetColorU32(ImGuiCol_SeparatorActive), style::uiScale); return; } @@ -417,7 +420,7 @@ namespace ImGui { // Finally, if nothing else was selected, just move the VFO if ((VFOMoveSingleClick ? ImGui::IsMouseClicked(ImGuiMouseButton_Left) : ImGui::IsMouseDown(ImGuiMouseButton_Left)) && (mouseInFFT | mouseInWaterfall) && (mouseMoved || hoveredVFOName == "")) { if (selVfo != NULL) { - int refCenter = mousePos.x - (widgetPos.x + 50); + int refCenter = mousePos.x - fftAreaMin.x; if (refCenter >= 0 && refCenter < dataWidth) { double off = ((((double)refCenter / ((double)dataWidth / 2.0)) - 1.0) * (viewBandwidth / 2.0)) + viewOffset; off += centerFreq; @@ -599,10 +602,10 @@ namespace ImGui { float bpBottom; if (bandPlanPos == BANDPLAN_POS_BOTTOM) { - bpBottom = widgetPos.y + fftHeight + 10; + bpBottom = fftAreaMax.y; } else { - bpBottom = widgetPos.y + height + 10; + bpBottom = fftAreaMin.y + height + 1; } @@ -620,9 +623,9 @@ namespace ImGui { start = std::clamp(start, lowerFreq, upperFreq); end = std::clamp(end, lowerFreq, upperFreq); center = (start + end) / 2.0; - aPos = widgetPos.x + 50 + ((start - lowerFreq) * horizScale); - bPos = widgetPos.x + 50 + ((end - lowerFreq) * horizScale); - cPos = widgetPos.x + 50 + ((center - lowerFreq) * horizScale); + aPos = fftAreaMin.x + ((start - lowerFreq) * horizScale); + bPos = fftAreaMin.x + ((end - lowerFreq) * horizScale); + cPos = fftAreaMin.x + ((center - lowerFreq) * horizScale); width = bPos - aPos; txtSz = ImGui::CalcTextSize(bandplan->bands[i].name.c_str()); if (bandplan::colorTable.find(bandplan->bands[i].type.c_str()) != bandplan::colorTable.end()) { @@ -633,22 +636,22 @@ namespace ImGui { color = IM_COL32(255, 255, 255, 255); colorTrans = IM_COL32(255, 255, 255, 100); } - if (aPos <= widgetPos.x + 50) { - aPos = widgetPos.x + 51; + if (aPos <= fftAreaMin.x) { + aPos = fftAreaMin.x + 1; } - if (bPos <= widgetPos.x + 50) { - bPos = widgetPos.x + 51; + if (bPos <= fftAreaMin.x) { + bPos = fftAreaMin.x + 1; } if (width >= 1.0) { window->DrawList->AddRectFilled(ImVec2(roundf(aPos), bpBottom - height), ImVec2(roundf(bPos), bpBottom), colorTrans); if (startVis) { window->DrawList->AddLine(ImVec2(roundf(aPos), bpBottom - height - 1), - ImVec2(roundf(aPos), bpBottom - 1), color); + ImVec2(roundf(aPos), bpBottom - 1), color, style::uiScale); } if (endVis) { window->DrawList->AddLine(ImVec2(roundf(bPos), bpBottom - height - 1), - ImVec2(roundf(bPos), bpBottom - 1), color); + ImVec2(roundf(bPos), bpBottom - 1), color, style::uiScale); } } if (txtSz.x <= width) { @@ -679,15 +682,15 @@ namespace ImGui { int lastWaterfallHeight = waterfallHeight; if (waterfallVisible) { - FFTAreaHeight = std::min(FFTAreaHeight, widgetSize.y - 50); + FFTAreaHeight = std::min(FFTAreaHeight, widgetSize.y - (50.0f * style::uiScale)); newFFTAreaHeight = FFTAreaHeight; - fftHeight = FFTAreaHeight - 50; - waterfallHeight = widgetSize.y - fftHeight - 52; + fftHeight = FFTAreaHeight - (50.0f * style::uiScale); + waterfallHeight = widgetSize.y - fftHeight - (50.0f * style::uiScale) - 2; } else { - fftHeight = widgetSize.y - 50; + fftHeight = widgetSize.y - (50.0f * style::uiScale); } - dataWidth = widgetSize.x - 60.0; + dataWidth = widgetSize.x - (60.0f * style::uiScale); if (waterfallVisible) { // Raw FFT resize @@ -724,12 +727,14 @@ namespace ImGui { latestFFT[i] = -1000.0; // Hide everything } - fftAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + 9); - fftAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 10); - freqAreaMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 11); - freqAreaMax = ImVec2(widgetPos.x + dataWidth + 50, widgetPos.y + fftHeight + 50); - wfMin = ImVec2(widgetPos.x + 50, widgetPos.y + fftHeight + 51); - wfMax = ImVec2(widgetPos.x + 50 + dataWidth, widgetPos.y + fftHeight + 51 + waterfallHeight); + fftAreaMin = ImVec2(widgetPos.x + (50.0f * style::uiScale), widgetPos.y + (9.0f * style::uiScale)); + fftAreaMax = ImVec2(fftAreaMin.x + dataWidth, fftAreaMin.y + fftHeight + 1); + + freqAreaMin = ImVec2(fftAreaMin.x, fftAreaMax.y + 1); + freqAreaMax = ImVec2(fftAreaMax.x, fftAreaMax.y + (40.0f * style::uiScale)); + + wfMin = ImVec2(fftAreaMin.x, freqAreaMax.y + 1); + wfMax = ImVec2(fftAreaMin.x + dataWidth, wfMin.y + waterfallHeight); maxHSteps = dataWidth / (ImGui::CalcTextSize("000.000").x + 10); maxVSteps = fftHeight / (ImGui::CalcTextSize("000.000").y); @@ -769,8 +774,8 @@ namespace ImGui { //window->DrawList->AddRectFilled(widgetPos, widgetEndPos, IM_COL32( 0, 0, 0, 255 )); ImU32 bg = ImGui::ColorConvertFloat4ToU32(gui::themeManager.waterfallBg); window->DrawList->AddRectFilled(widgetPos, widgetEndPos, bg); - window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32(50, 50, 50, 255)); - window->DrawList->AddLine(ImVec2(widgetPos.x, widgetPos.y + fftHeight + 50), ImVec2(widgetPos.x + widgetSize.x, widgetPos.y + fftHeight + 50), IM_COL32(50, 50, 50, 255), 1.0); + window->DrawList->AddRect(widgetPos, widgetEndPos, IM_COL32(50, 50, 50, 255), 0.0, 0, style::uiScale); + window->DrawList->AddLine(ImVec2(widgetPos.x, freqAreaMax.y), ImVec2(widgetPos.x + widgetSize.x, freqAreaMax.y), IM_COL32(50, 50, 50, 255), style::uiScale); if (!gui::mainWindow.lockWaterfallControls) { inputHandled = false; @@ -1041,8 +1046,8 @@ namespace ImGui { vfo->updateDrawingVars(viewBandwidth, dataWidth, viewOffset, widgetPos, fftHeight); vfo->wfRectMin = ImVec2(vfo->rectMin.x, wfMin.y); vfo->wfRectMax = ImVec2(vfo->rectMax.x, wfMax.y); - vfo->wfLineMin = ImVec2(vfo->lineMin.x, wfMin.y); - vfo->wfLineMax = ImVec2(vfo->lineMax.x, wfMax.y); + vfo->wfLineMin = ImVec2(vfo->lineMin.x, wfMin.y - 1); + vfo->wfLineMax = ImVec2(vfo->lineMax.x, wfMax.y - 1); vfo->wfLbwSelMin = ImVec2(vfo->wfRectMin.x - 2, vfo->wfRectMin.y); vfo->wfLbwSelMax = ImVec2(vfo->wfRectMin.x + 2, vfo->wfRectMax.y); vfo->wfRbwSelMin = ImVec2(vfo->wfRectMax.x - 2, vfo->wfRectMin.y); @@ -1175,16 +1180,16 @@ namespace ImGui { // Calculate the position of the line if (reference == REF_LOWER) { - lineMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 9); - lineMax = ImVec2(widgetPos.x + 50 + left, widgetPos.y + fftHeight + 9); + lineMin = ImVec2(gui::waterfall.fftAreaMin.x + left, gui::waterfall.fftAreaMin.y); + lineMax = ImVec2(gui::waterfall.fftAreaMin.x + left, gui::waterfall.fftAreaMax.y - 1); } else if (reference == REF_CENTER) { - lineMin = ImVec2(widgetPos.x + 50 + center, widgetPos.y + 9); - lineMax = ImVec2(widgetPos.x + 50 + center, widgetPos.y + fftHeight + 9); + lineMin = ImVec2(gui::waterfall.fftAreaMin.x + center, gui::waterfall.fftAreaMin.y); + lineMax = ImVec2(gui::waterfall.fftAreaMin.x + center, gui::waterfall.fftAreaMax.y - 1); } else if (reference == REF_UPPER) { - lineMin = ImVec2(widgetPos.x + 50 + right, widgetPos.y + 9); - lineMax = ImVec2(widgetPos.x + 50 + right, widgetPos.y + fftHeight + 9); + lineMin = ImVec2(gui::waterfall.fftAreaMin.x + right, gui::waterfall.fftAreaMin.y); + lineMax = ImVec2(gui::waterfall.fftAreaMin.x + right, gui::waterfall.fftAreaMax.y - 1); } int _left = left; @@ -1194,22 +1199,23 @@ namespace ImGui { leftClamped = (left != _left); rightClamped = (right != _right); - rectMin = ImVec2(widgetPos.x + 50 + left, widgetPos.y + 10); - rectMax = ImVec2(widgetPos.x + 51 + right, widgetPos.y + fftHeight + 10); + rectMin = ImVec2(gui::waterfall.fftAreaMin.x + left, gui::waterfall.fftAreaMin.y + 1); + rectMax = ImVec2(gui::waterfall.fftAreaMin.x + right + 1, gui::waterfall.fftAreaMax.y); - lbwSelMin = ImVec2(rectMin.x - 2, rectMin.y); - lbwSelMax = ImVec2(rectMin.x + 2, rectMax.y); - rbwSelMin = ImVec2(rectMax.x - 2, rectMin.y); - rbwSelMax = ImVec2(rectMax.x + 2, rectMax.y); + float gripSize = 2.0f * style::uiScale; + lbwSelMin = ImVec2(rectMin.x - gripSize, rectMin.y); + lbwSelMax = ImVec2(rectMin.x + gripSize, rectMax.y); + rbwSelMin = ImVec2(rectMax.x - gripSize, rectMin.y); + rbwSelMax = ImVec2(rectMax.x + gripSize, rectMax.y); - notchMin = ImVec2(widgetPos.x + 50 + notch - 2, widgetPos.y + 9); - notchMax = ImVec2(widgetPos.x + 50 + notch + 2, widgetPos.y + fftHeight + 9); + notchMin = ImVec2(gui::waterfall.fftAreaMin.x + notch - gripSize, gui::waterfall.fftAreaMin.y); + notchMax = ImVec2(gui::waterfall.fftAreaMin.x + notch + gripSize, gui::waterfall.fftAreaMax.y - 1); } void WaterfallVFO::draw(ImGuiWindow* window, bool selected) { window->DrawList->AddRectFilled(rectMin, rectMax, color); if (lineVisible) { - window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255)); + window->DrawList->AddLine(lineMin, lineMax, selected ? IM_COL32(255, 0, 0, 255) : IM_COL32(255, 255, 0, 255), style::uiScale); } if (notchVisible) { diff --git a/core/src/gui/widgets/waterfall.h b/core/src/gui/widgets/waterfall.h index b68a546d..6373215c 100644 --- a/core/src/gui/widgets/waterfall.h +++ b/core/src/gui/widgets/waterfall.h @@ -236,6 +236,13 @@ namespace ImGui { _BANDPLAN_POS_COUNT }; + ImVec2 fftAreaMin; + ImVec2 fftAreaMax; + ImVec2 freqAreaMin; + ImVec2 freqAreaMax; + ImVec2 wfMin; + ImVec2 wfMax; + private: void drawWaterfall(); void drawFFT(); @@ -260,13 +267,6 @@ namespace ImGui { ImVec2 lastWidgetPos; ImVec2 lastWidgetSize; - ImVec2 fftAreaMin; - ImVec2 fftAreaMax; - ImVec2 freqAreaMin; - ImVec2 freqAreaMax; - ImVec2 wfMin; - ImVec2 wfMax; - ImGuiWindow* window; GLuint textureId; diff --git a/core/src/module.cpp b/core/src/module.cpp index 1889ba68..8689ca58 100644 --- a/core/src/module.cpp +++ b/core/src/module.cpp @@ -4,6 +4,9 @@ ModuleManager::Module_t ModuleManager::loadModule(std::string path) { Module_t mod; + + // On android, the path has to be relative, don't make it absolute +#ifndef __ANDROID__ if (!std::filesystem::exists(path)) { spdlog::error("{0} does not exist", path); mod.handle = NULL; @@ -14,6 +17,7 @@ ModuleManager::Module_t ModuleManager::loadModule(std::string path) { mod.handle = NULL; return mod; } +#endif #ifdef _WIN32 mod.handle = LoadLibraryA(path.c_str()); if (mod.handle == NULL) { diff --git a/core/src/options.cpp b/core/src/options.cpp index 6e8df2ee..296bd363 100644 --- a/core/src/options.cpp +++ b/core/src/options.cpp @@ -13,6 +13,8 @@ namespace options { #elif defined(IS_MACOS_BUNDLE) std::string homedir = getenv("HOME"); opts.root = homedir + "/Library/Application Support/sdrpp"; +#elif defined(__ANDROID__) + opts.root = "/storage/self/primary/sdrpp"; #else std::string homedir = getenv("HOME"); opts.root = homedir + "/.config/sdrpp"; diff --git a/core/src/signal_path/sink.cpp b/core/src/signal_path/sink.cpp index 7f62838f..5435d2f9 100644 --- a/core/src/signal_path/sink.cpp +++ b/core/src/signal_path/sink.cpp @@ -248,6 +248,7 @@ void SinkManager::showVolumeSlider(std::string name, std::string prefix, float w } float ypos = ImGui::GetCursorPosY(); + float sliderOffset = 8.0f * style::uiScale; if (streams.find(name) == streams.end() || name == "") { float dummy = 0.0f; @@ -256,7 +257,7 @@ void SinkManager::showVolumeSlider(std::string name, std::string prefix, float w ImGui::ImageButton(icons::MUTED, ImVec2(height, height), ImVec2(0, 0), ImVec2(1, 1), btwBorder); ImGui::PopID(); ImGui::SameLine(); - ImGui::SetNextItemWidth(width - height - 8); + ImGui::SetNextItemWidth(width - height - sliderOffset); ImGui::SetCursorPosY(ypos + ((height - sliderHeight) / 2.0f) + btwBorder); ImGui::SliderFloat((prefix + name).c_str(), &dummy, 0.0f, 1.0f, ""); style::endDisabled(); @@ -289,7 +290,7 @@ void SinkManager::showVolumeSlider(std::string name, std::string prefix, float w ImGui::SameLine(); - ImGui::SetNextItemWidth(width - height - 8); + ImGui::SetNextItemWidth(width - height - sliderOffset); ImGui::SetCursorPosY(ypos + ((height - sliderHeight) / 2.0f) + btwBorder); if (ImGui::SliderFloat((prefix + name).c_str(), &stream->guiVolume, 0.0f, 1.0f, "")) { stream->setVolume(stream->guiVolume); diff --git a/core/src/utils/opengl_include_code.h b/core/src/utils/opengl_include_code.h index 1e4e90cb..b5a3ec18 100644 --- a/core/src/utils/opengl_include_code.h +++ b/core/src/utils/opengl_include_code.h @@ -5,6 +5,9 @@ #include #elif defined(__APPLE__) #include +#elif defined(__ANDROID__) +#include +#include #else #include #endif \ No newline at end of file diff --git a/core/src/version.h b/core/src/version.h index 590d8bd0..f1d72943 100644 --- a/core/src/version.h +++ b/core/src/version.h @@ -1,3 +1,3 @@ #pragma once -#define VERSION_STR "1.0.5" \ No newline at end of file +#define VERSION_STR "1.0.6" \ No newline at end of file diff --git a/root/res/bandplans/united-kingdom.json b/root/res/bandplans/united-kingdom.json index 2676b71b..589e2398 100644 --- a/root/res/bandplans/united-kingdom.json +++ b/root/res/bandplans/united-kingdom.json @@ -3,7 +3,7 @@ "country_name": "United Kingdom", "country_code": "UK", "author_name": "John Donkersley", - "author_url": "--", + "author_url": "", "bands": [ { "name": "Long Wave", @@ -29,18 +29,42 @@ "start": 1810000, "end": 2000000 }, + { + "name": "Maritime", + "type": "marine", + "start": 2045000, + "end": 2300000 + }, { "name": "120m Broadcast", "type": "broadcast", "start": 2300000, "end": 2495000 }, + { + "name": "Maritime", + "type": "marine", + "start": 2500000, + "end": 2850000 + }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 2850000, + "end": 3155000 + }, { "name": "90m Broadcast", "type": "broadcast", "start": 3200000, "end": 3400000 }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 3400000, + "end": 3500000 + }, { "name": "80m Ham Band", "type": "amateur", @@ -53,6 +77,18 @@ "start": 3900000, "end": 4000000 }, + { + "name": "Maritime", + "type": "marine", + "start": 4063000, + "end": 4438000 + }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 4650000, + "end": 4750000 + }, { "name": "60m Broadcast", "type": "broadcast", @@ -65,12 +101,30 @@ "start": 5258500, "end": 5406500 }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 5450000, + "end": 5730000 + }, { "name": "49m Broadcast", "type": "broadcast", "start": 5900000, "end": 6200000 }, + { + "name": "Maritime", + "type": "marine", + "start": 6200000, + "end": 6525000 + }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 6525000, + "end": 6765000 + }, { "name": "40m Ham Band", "type": "amateur", @@ -83,23 +137,59 @@ "start": 7200000, "end": 7450000 }, + { + "name": "Maritime", + "type": "marine", + "start": 8195000, + "end": 8815000 + }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 8815000, + "end": 9040000 + }, { "name": "31m Broadcast", "type": "broadcast", "start": 9400000, "end": 9900000 }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 10005000, + "end": 10100000 + }, { "name": "30m Ham Band", "type": "amateur", "start": 10100000, "end": 10150000 }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 11175000, + "end": 11400000 + }, { "name": "25m Broadcast", "type": "broadcast", "start": 11600000, - "end": 12100000 + "end": 12230000 + }, + { + "name": "Maritime", + "type": "marine", + "start": 12230000, + "end": 13200000 + }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 13200000, + "end": 13360000 }, { "name": "22m Broadcast", @@ -113,6 +203,18 @@ "start": 14000000, "end": 14350000 }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 15010000, + "end": 15100000 + }, + { + "name": "Maritime", + "type": "marine", + "start": 16360000, + "end": 17410000 + }, { "name": "19m Broadcast", "type": "broadcast", @@ -125,18 +227,36 @@ "start": 17480000, "end": 17900000 }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 17900000, + "end": 18030000 + }, { "name": "17m Ham Band", "type": "amateur", "start": 18068000, "end": 18168000 }, + { + "name": "Maritime - ship tx", + "type": "marine", + "start": 18780000, + "end": 18900000 + }, { "name": "15m Broadcast", "type": "broadcast", "start": 18900000, "end": 19020000 }, + { + "name": "Maritime - coast tx", + "type": "marine", + "start": 19680000, + "end": 19990000 + }, { "name": "15m Ham Band", "type": "amateur", @@ -149,6 +269,18 @@ "start": 21450000, "end": 21850000 }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 21870000, + "end": 22000000 + }, + { + "name": "Aeronautical Mobile", + "type": "aviation", + "start": 23200000, + "end": 23350000 + }, { "name": "12m Ham Band", "type": "amateur", @@ -179,6 +311,18 @@ "start": 28000000, "end": 29700000 }, + { + "name": "Analogue Cordless Phones", + "type": "amateur", + "start": 31037500, + "end": 40112500 + }, + { + "name": "Low Power Devices", + "type": "amateur", + "start": 49820000, + "end": 49987500 + }, { "name": "6m Ham Band", "type": "amateur", @@ -198,7 +342,7 @@ "end": 108000000 }, { - "name": "Air Band VOR/ILS", + "name": "Air Band TACAN/ILS", "type": "aviation", "start": 108000000, "end": 118000000 @@ -206,7 +350,7 @@ { "name": "Air Band Voice", "type": "aviation", - "start": 118000000, + "start": 117975000, "end": 137000000 }, { @@ -222,11 +366,53 @@ "end": 146000000 }, { - "name": "Marine", + "name": "Land/Mountain Rescue", + "type": "PMR", + "start": 147343750, + "end": 147500000 + }, + { + "name": "Pagers - Flex/POCSAG", + "type": "PMR", + "start": 153025000, + "end": 153500000 + }, + { + "name": "Land/Mountain Rescue DMR", + "type": "PMR", + "start": 155000000, + "end": 156000000 + }, + { + "name": "Marine - ship tx", "type": "marine", "start": 156000000, + "end": 157850000 + }, + { + "name": "Short Term Hire", + "type": "PMR", + "start": 158787500, + "end": 159687500 + }, + { + "name": "Marine - coast tx", + "type": "marine", + "start": 160650000, "end": 162025000 }, + { + "name": "Short Term Hire", + "type": "PMR", + "start": 163287500, + "end": 164200000 + }, + { + "name": "Business Radio", + "type": "PMR", + "start": 165000000, + "end": 174000000 + }, { "name": "DAB Radio", "type": "broadcast", @@ -239,6 +425,12 @@ "start": 230000000, "end": 400000000 }, + { + "name": "Private Mobile Radio inc trams", + "type": "PMR", + "start": 422000000, + "end": 424000000 + }, { "name": "70cm Ham Band", "type": "amateur", @@ -247,16 +439,52 @@ }, { "name": "PMR446", - "type": "amateur", + "type": "PMR", "start": 446000000, "end": 446200000 }, + { + "name": "Outside Broadcast Talkback", + "type": "PMR", + "start": 446200000, + "end": 447512500 + }, + { + "name": "Private Mobile Radio", + "type": "PMR", + "start": 447600000, + "end": 454000000 + }, + { + "name": "Mosques", + "type": "amateur", + "start": 454000000, + "end": 455000000 + }, + { + "name": "Private Mobile Radio", + "type": "PMR", + "start": 455000000, + "end": 467200000 + }, + { + "name": "Outside Broadcast Talkback", + "type": "PMR", + "start": 467200000, + "end": 468600000 + }, { "name": "Digital TV Broadcast", "type": "broadcast", "start": 470000000, "end": 790000000 }, + { + "name": "Licence Exempt Short Range", + "type": "amateur", + "start": 862000000, + "end": 875800000 + }, { "name": "23cm Ham Band", "type": "amateur", diff --git a/source_modules/airspy_source/CMakeLists.txt b/source_modules/airspy_source/CMakeLists.txt index e69bf710..4884dc72 100644 --- a/source_modules/airspy_source/CMakeLists.txt +++ b/source_modules/airspy_source/CMakeLists.txt @@ -24,6 +24,16 @@ if (MSVC) target_include_directories(airspy_source PUBLIC "C:/Program Files/PothosSDR/include/libairspy/") target_link_libraries(airspy_source PRIVATE airspy) +elseif (ANDROID) + target_include_directories(sdrpp_core PUBLIC + /mnt/android_sdr/libusb/libusb + /mnt/android_sdr/airspyone_host/libairspy/src + ) + + target_link_libraries(sdrpp_core PUBLIC + /mnt/android_sdr/output/libusb/${ANDROID_ABI}/libusb1.0.so + /mnt/android_sdr/output/libairspy/${ANDROID_ABI}/libairspy.so + ) else (MSVC) find_package(PkgConfig) diff --git a/source_modules/airspy_source/src/main.cpp b/source_modules/airspy_source/src/main.cpp index 101acb9f..6a9d0493 100644 --- a/source_modules/airspy_source/src/main.cpp +++ b/source_modules/airspy_source/src/main.cpp @@ -10,6 +10,10 @@ #include #include +#ifdef __ANDROID__ +#include +#endif + #define CONCAT(a, b) ((std::string(a) + b).c_str()) SDRPP_MOD_INFO{ @@ -75,6 +79,7 @@ public: } void refresh() { +#ifndef __ANDROID__ devList.clear(); devListTxt = ""; @@ -88,6 +93,18 @@ public: devListTxt += buf; devListTxt += '\0'; } +#else + // Check for device presence + int vid, pid; + devFd = backend::getDeviceFD(vid, pid, backend::AIRSPY_VIDPIDS); + if (devFd < 0) { return; } + + // Get device info + std::string fakeName = "Airspy USB"; + devList.push_back(0xDEADBEEF); + devListTxt += fakeName; + devListTxt += '\0'; +#endif } void selectFirst() { @@ -112,7 +129,11 @@ public: void selectBySerial(uint64_t serial) { airspy_device* dev; try { +#ifndef __ANDROID__ int err = airspy_open_sn(&dev, serial); +#else + int err = airspy_open_sn(&dev, devFd); +#endif if (err != 0) { char buf[1024]; sprintf(buf, "%016" PRIX64, serial); @@ -245,7 +266,11 @@ private: return; } +#ifndef __ANDROID__ int err = airspy_open_sn(&_this->openDev, _this->selectedSerial); +#else + int err = airspy_open_sn(&_this->openDev, _this->devFd); +#endif if (err != 0) { char buf[1024]; sprintf(buf, "%016" PRIX64, _this->selectedSerial); @@ -571,6 +596,10 @@ private: bool lnaAgc = false; bool mixerAgc = false; +#ifdef __ANDROID__ + int devFd = 0; +#endif + std::vector devList; std::string devListTxt; std::vector sampleRateList; diff --git a/source_modules/airspyhf_source/CMakeLists.txt b/source_modules/airspyhf_source/CMakeLists.txt index c224e109..5a52b091 100644 --- a/source_modules/airspyhf_source/CMakeLists.txt +++ b/source_modules/airspyhf_source/CMakeLists.txt @@ -24,6 +24,16 @@ if (MSVC) target_include_directories(airspyhf_source PUBLIC "C:/Program Files/PothosSDR/include/libairspyhf/") target_link_libraries(airspyhf_source PRIVATE airspyhf) +elseif (ANDROID) + target_include_directories(sdrpp_core PUBLIC + /mnt/android_sdr/libusb/libusb + /mnt/android_sdr/airspyhf/libairspyhf/src + ) + + target_link_libraries(sdrpp_core PUBLIC + /mnt/android_sdr/output/libusb/${ANDROID_ABI}/libusb1.0.so + /mnt/android_sdr/output/libairspyhf/${ANDROID_ABI}/libairspyhf.so + ) else (MSVC) find_package(PkgConfig) diff --git a/source_modules/airspyhf_source/src/main.cpp b/source_modules/airspyhf_source/src/main.cpp index 48f26bf0..65b8a7e0 100644 --- a/source_modules/airspyhf_source/src/main.cpp +++ b/source_modules/airspyhf_source/src/main.cpp @@ -10,6 +10,10 @@ #include #include +#ifdef __ANDROID__ +#include +#endif + #define CONCAT(a, b) ((std::string(a) + b).c_str()) SDRPP_MOD_INFO{ @@ -79,6 +83,7 @@ public: devList.clear(); devListTxt = ""; +#ifndef __ANDROID__ uint64_t serials[256]; int n = airspyhf_list_devices(serials, 256); @@ -89,6 +94,18 @@ public: devListTxt += buf; devListTxt += '\0'; } +#else + // Check for device presence + int vid, pid; + devFd = backend::getDeviceFD(vid, pid, backend::AIRSPYHF_VIDPIDS); + if (devFd < 0) { return; } + + // Get device info + std::string fakeName = "Airspy HF+ USB"; + devList.push_back(0xDEADBEEF); + devListTxt += fakeName; + devListTxt += '\0'; +#endif } void selectFirst() { @@ -113,10 +130,14 @@ public: void selectBySerial(uint64_t serial) { airspyhf_device_t* dev; try { - int err = airspyhf_open_sn(&dev, selectedSerial); +#ifndef __ANDROID__ + int err = airspyhf_open_sn(&dev, serial); +#else + int err = airspyhf_open_sn(&dev, devFd); +#endif if (err != 0) { char buf[1024]; - sprintf(buf, "%016" PRIX64, selectedSerial); + sprintf(buf, "%016" PRIX64, serial); spdlog::error("Could not open Airspy HF+ {0}", buf); selectedSerial = 0; return; @@ -124,7 +145,7 @@ public: } catch (std::exception e) { char buf[1024]; - sprintf(buf, "%016" PRIX64, selectedSerial); + sprintf(buf, "%016" PRIX64, serial); spdlog::error("Could not open Airspy HF+ {0}", buf); } @@ -221,7 +242,11 @@ private: return; } - int err = airspyhf_open_sn(&_this->openDev, _this->selectedSerial); +#ifndef __ANDROID__ + int err = airspyhf_open_sn(&_this->openDev, _this->selectedSerial); +#else + int err = airspyhf_open_sn(&_this->openDev, _this->devFd); +#endif if (err != 0) { char buf[1024]; sprintf(buf, "%016" PRIX64, _this->selectedSerial); @@ -368,6 +393,10 @@ private: float atten = 0.0f; std::string selectedSerStr = ""; +#ifdef __ANDROID__ + int devFd = 0; +#endif + std::vector devList; std::string devListTxt; std::vector sampleRateList; diff --git a/source_modules/hackrf_source/CMakeLists.txt b/source_modules/hackrf_source/CMakeLists.txt index 5f8b1ee7..3fe78b54 100644 --- a/source_modules/hackrf_source/CMakeLists.txt +++ b/source_modules/hackrf_source/CMakeLists.txt @@ -22,6 +22,16 @@ if (MSVC) target_link_directories(hackrf_source PRIVATE "C:/Program Files/PothosSDR/bin/") target_link_libraries(hackrf_source PRIVATE hackrf) +elseif (ANDROID) + target_include_directories(sdrpp_core PUBLIC + /mnt/android_sdr/libusb/libusb + /mnt/android_sdr/hackrf/host/libhackrf/src + ) + + target_link_libraries(sdrpp_core PUBLIC + /mnt/android_sdr/output/libusb/${ANDROID_ABI}/libusb1.0.so + /mnt/android_sdr/output/libhackrf/${ANDROID_ABI}/libhackrf.so + ) else (MSVC) find_package(PkgConfig) diff --git a/source_modules/hackrf_source/src/main.cpp b/source_modules/hackrf_source/src/main.cpp index 593cb1c7..2c549103 100644 --- a/source_modules/hackrf_source/src/main.cpp +++ b/source_modules/hackrf_source/src/main.cpp @@ -5,11 +5,18 @@ #include #include #include -#include #include #include #include +#ifndef __ANDROID__ +#include +#else +#include +#include +#include +#endif + #define CONCAT(a, b) ((std::string(a) + b).c_str()) SDRPP_MOD_INFO{ @@ -127,6 +134,7 @@ public: devList.clear(); devListTxt = ""; +#ifndef __ANDROID__ uint64_t serials[256]; hackrf_device_list_t* _devList = hackrf_device_list(); @@ -137,6 +145,15 @@ public: } hackrf_device_list_free(_devList); +#else + int vid, pid; + devFd = backend::getDeviceFD(vid, pid, backend::HACKRF_VIDPIDS); + if (devFd < 0) { return; } + std::string fakeName = "HackRF USB"; + devList.push_back("fake_serial"); + devListTxt += fakeName; + devListTxt += '\0'; +#endif } void selectFirst() { @@ -229,7 +246,11 @@ private: return; } +#ifndef __ANDROID__ hackrf_error err = (hackrf_error)hackrf_open_by_serial(_this->selectedSerial.c_str(), &_this->openDev); +#else + hackrf_error err = (hackrf_error)hackrf_open_by_fd(&_this->openDev, _this->devFd); +#endif if (err != HACKRF_SUCCESS) { spdlog::error("Could not open HackRF {0}: {1}", _this->selectedSerial, hackrf_error_name(err)); return; @@ -383,6 +404,10 @@ private: float lna = 0; float vga = 0; +#ifdef __ANDROID__ + int devFd = -1; +#endif + std::vector devList; std::string devListTxt; }; diff --git a/source_modules/plutosdr_source/CMakeLists.txt b/source_modules/plutosdr_source/CMakeLists.txt index 1b9ef841..bbd8ea40 100644 --- a/source_modules/plutosdr_source/CMakeLists.txt +++ b/source_modules/plutosdr_source/CMakeLists.txt @@ -24,6 +24,17 @@ if (MSVC) target_link_libraries(plutosdr_source PRIVATE libiio) target_link_libraries(plutosdr_source PRIVATE libad9361) +elseif (ANDROID) + target_include_directories(sdrpp_core PUBLIC + /mnt/android_sdr/libiio + /mnt/android_sdr/libad9361-iio + ) + + target_link_libraries(sdrpp_core PUBLIC + /mnt/android_sdr/output/libxml2/${ANDROID_ABI}/libxml2.so + /mnt/android_sdr/output/libiio/${ANDROID_ABI}/libiio.so + /mnt/android_sdr/output/libad9361/${ANDROID_ABI}/libad9361.so + ) else (MSVC) if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") target_include_directories(plutosdr_source PRIVATE "/Library/Frameworks/iio.framework/Headers") diff --git a/source_modules/rtl_sdr_source/CMakeLists.txt b/source_modules/rtl_sdr_source/CMakeLists.txt index ca61c78c..bbd6d950 100644 --- a/source_modules/rtl_sdr_source/CMakeLists.txt +++ b/source_modules/rtl_sdr_source/CMakeLists.txt @@ -22,6 +22,16 @@ if (MSVC) target_link_directories(rtl_sdr_source PRIVATE "C:/Program Files/PothosSDR/bin/") target_link_libraries(rtl_sdr_source PRIVATE rtlsdr) +elseif (ANDROID) + target_include_directories(sdrpp_core PUBLIC + /mnt/android_sdr/libusb/libusb + /mnt/android_sdr/librtlsdr/include + ) + + target_link_libraries(sdrpp_core PUBLIC + /mnt/android_sdr/output/libusb/${ANDROID_ABI}/libusb1.0.so + /mnt/android_sdr/output/librtlsdr/${ANDROID_ABI}/librtlsdr.so + ) else (MSVC) find_package(PkgConfig) diff --git a/source_modules/rtl_sdr_source/src/main.cpp b/source_modules/rtl_sdr_source/src/main.cpp index 6265c05d..eb2180e8 100644 --- a/source_modules/rtl_sdr_source/src/main.cpp +++ b/source_modules/rtl_sdr_source/src/main.cpp @@ -9,6 +9,9 @@ #include #include +#ifdef __ANDROID__ +#include +#endif #define CONCAT(a, b) ((std::string(a) + b).c_str()) @@ -114,6 +117,7 @@ public: devNames.clear(); devListTxt = ""; +#ifndef __ANDROID__ devCount = rtlsdr_get_device_count(); char buf[1024]; for (int i = 0; i < devCount; i++) { @@ -123,6 +127,20 @@ public: devListTxt += buf; devListTxt += '\0'; } +#else + // Check for device connection + devCount = 0; + int vid, pid; + devFd = backend::getDeviceFD(vid, pid, backend::RTL_SDR_VIDPIDS); + if (devFd < 0) { return; } + + // Generate fake device info + devCount = 1; + std::string fakeName = "RTL-SDR Dongle USB"; + devNames.push_back(fakeName); + devListTxt += fakeName; + devListTxt += '\0'; +#endif } void selectFirst() { @@ -144,9 +162,15 @@ public: void selectById(int id) { selectedDevName = devNames[id]; - if (rtlsdr_open(&openDev, id) < 0) { +#ifndef __ANDROID__ + int oret = rtlsdr_open(&openDev, id); +#else + int oret = rtlsdr_open(&openDev, devFd); +#endif + + if (oret < 0) { selectedDevName = ""; - spdlog::error("Could not open RTL-SDR"); + spdlog::error("Could not open RTL-SDR: {0}", oret); return; } @@ -252,7 +276,13 @@ private: return; } - if (rtlsdr_open(&_this->openDev, _this->devId) < 0) { +#ifndef __ANDROID__ + int oret = rtlsdr_open(&_this->openDev, _this->devId); +#else + int oret = rtlsdr_open(&_this->openDev, _this->devFd); +#endif + + if (oret < 0) { spdlog::error("Could not open RTL-SDR"); return; } @@ -510,6 +540,10 @@ private: int devCount = 0; std::thread workerThread; +#ifdef __ANDROID__ + int devFd = -1; +#endif + int ppm = 0; bool biasT = false;