cmake_minimum_required(VERSION 3.24) project(udpscope VERSION 0.1.0 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE Release CACHE STRING "" FORCE) endif() find_package(Threads REQUIRED) # --- libadwaita: the shortcut overview --------------------------------------- # GTK4's own `GtkShortcutsWindow` is deprecated as of 4.18 and libadwaita's # `AdwShortcutsDialog` (1.8) is its replacement, so the overview is built from the # platform widget rather than from a hand-rolled grid. This is the only thing libadwaita # is used for; `adw_init()` is called for it, which also means the app picks up # libadwaita's stylesheet. find_package(PkgConfig REQUIRED) pkg_check_modules(ADWAITA REQUIRED IMPORTED_TARGET libadwaita-1>=1.8) # --- deps/plotter: built from the submodule, GTK4 adapter only ---------------- set(PLOT_BUILD_TESTS OFF CACHE BOOL "" FORCE) set(PLOT_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set(PLOT_BUILD_GLFW OFF CACHE BOOL "" FORCE) set(PLOT_BUILD_IMGUI OFF CACHE BOOL "" FORCE) set(PLOT_BUILD_QT6 OFF CACHE BOOL "" FORCE) set(PLOT_BUILD_GTK4 ON CACHE BOOL "" FORCE) set(PLOT_INSTALL OFF CACHE BOOL "" FORCE) add_subdirectory(deps/plotter EXCLUDE_FROM_ALL) # --- deps/udpsclient: vendored C99 receiver ---------------------------------- add_library(udpsclient STATIC deps/udpsclient/udps_client.c) target_include_directories(udpsclient PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/deps/udpsclient) set_target_properties(udpsclient PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON) if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(udpsclient PRIVATE -Wall -Wextra) endif() # --- icons: six SVGs compiled into the binary -------------------------------- # There is no stock icon for an oscilloscope trigger, a pair of cursors or a measurement # ruler, so this app draws its own. They go in a GResource rather than beside the binary # because udpscope is run straight out of the build tree and has no install step: a # search path would have to be located at runtime, and the failure mode of not locating # it is a silent missing-image placeholder. find_program(GLIB_COMPILE_RESOURCES NAMES glib-compile-resources REQUIRED) set(UDPSCOPE_ICON_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/icons) file(GLOB UDPSCOPE_ICON_FILES CONFIGURE_DEPENDS ${UDPSCOPE_ICON_DIR}/scalable/actions/*.svg ${UDPSCOPE_ICON_DIR}/scalable/apps/*.svg) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/udpscope_resources.c COMMAND ${GLIB_COMPILE_RESOURCES} --target=${CMAKE_CURRENT_BINARY_DIR}/udpscope_resources.c --sourcedir=${UDPSCOPE_ICON_DIR} --generate-source ${UDPSCOPE_ICON_DIR}/udpscope.gresource.xml DEPENDS ${UDPSCOPE_ICON_DIR}/udpscope.gresource.xml ${UDPSCOPE_ICON_FILES} COMMENT "Compiling udpscope icon resources" VERBATIM) # --- help pages: Pango markup in a GResource ---------------------------------- # A second bundle beside the icons'. --c-name is given explicitly so the two generated # sources cannot collide on symbol names. set(UDPSCOPE_HELP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/src/help) file(GLOB UDPSCOPE_HELP_FILES CONFIGURE_DEPENDS ${UDPSCOPE_HELP_DIR}/*.txt) add_custom_command( OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/udpscope_help_resources.c COMMAND ${GLIB_COMPILE_RESOURCES} --target=${CMAKE_CURRENT_BINARY_DIR}/udpscope_help_resources.c --sourcedir=${UDPSCOPE_HELP_DIR} --c-name=udpscope_help --generate-source ${UDPSCOPE_HELP_DIR}/udpscope-help.gresource.xml DEPENDS ${UDPSCOPE_HELP_DIR}/udpscope-help.gresource.xml ${UDPSCOPE_HELP_FILES} COMMENT "Compiling udpscope help resources" VERBATIM) # --- build info: version, commit and timestamp compiled into the binary ------- # Written at configure time so add_executable can name the file, and again on every # build so the commit and the timestamp belong to the binary that is running rather # than to whenever the tree was last configured. set(UDPSCOPE_BUILD_INFO ${CMAKE_CURRENT_BINARY_DIR}/build_info.cpp) set(UDPSCOPE_BUILD_INFO_ARGS -DSRC=${CMAKE_CURRENT_SOURCE_DIR} -DOUT=${UDPSCOPE_BUILD_INFO} -DVERSION=${PROJECT_VERSION} -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/write_build_info.cmake) execute_process(COMMAND ${CMAKE_COMMAND} ${UDPSCOPE_BUILD_INFO_ARGS}) add_custom_target(udpscope_build_info BYPRODUCTS ${UDPSCOPE_BUILD_INFO} COMMAND ${CMAKE_COMMAND} ${UDPSCOPE_BUILD_INFO_ARGS} COMMENT "Writing udpscope build info" VERBATIM) set_source_files_properties(${UDPSCOPE_BUILD_INFO} PROPERTIES GENERATED TRUE) # --- the application ---------------------------------------------------------- add_executable(udpscope ${CMAKE_CURRENT_BINARY_DIR}/udpscope_resources.c ${CMAKE_CURRENT_BINARY_DIR}/udpscope_help_resources.c ${UDPSCOPE_BUILD_INFO} src/main.cpp src/config.cpp src/channel.cpp src/model.cpp src/condition.cpp src/trigger.cpp src/capture.cpp src/waveform.cpp src/event_store.cpp src/acquisition.cpp src/plot_host.cpp src/export.cpp src/search.cpp src/layout_store.cpp src/source_dialog.cpp src/measure.cpp src/math_channel.cpp src/status_text.cpp src/shortcuts.cpp src/help_window.cpp src/ui.cpp src/ui_probe.cpp src/ui_widgets.cpp src/ui_events.cpp src/ui_status.cpp src/ui_measure.cpp src/ui_cursors.cpp src/ui_math.cpp src/ui_layout.cpp src/ui_trigger.cpp src/ui_signals.cpp ) target_include_directories(udpscope PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src) target_link_libraries(udpscope PRIVATE plot::gtk4 udpsclient Threads::Threads PkgConfig::ADWAITA) add_dependencies(udpscope udpscope_build_info) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") target_compile_options(udpscope PRIVATE -Wall -Wextra -Wpedantic) endif() # --- the desktop entry: opt-in, never part of `cmake --build` ------------------ # `Exec` is the built binary's absolute path in the build tree, so the entry always launches # exactly the binary that was last built -- and moving or deleting the build directory breaks # it. That is the price of having no install step, and it is the right one here: an install() # rule would create a second copy of the app that can diverge from the build tree, and a # documented `cp` would need the binary on PATH, which it is not. # # configure_file() cannot evaluate a generator expression and file(GENERATE) cannot substitute # an @VAR@, so the two run in sequence: the first writes the genex out literally, the second # evaluates it. This is the documented idiom. set(UDPSCOPE_EXEC "$") configure_file(${CMAKE_CURRENT_SOURCE_DIR}/data/org.udpscope.app.desktop.in ${CMAKE_CURRENT_BINARY_DIR}/desktop-entry.in @ONLY) file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/org.udpscope.app.desktop INPUT ${CMAKE_CURRENT_BINARY_DIR}/desktop-entry.in) # No ALL: this runs only when it is named, so `cmake --build build` is unaffected. add_custom_target(desktop-entry COMMAND ${CMAKE_COMMAND} -DENTRY=${CMAKE_CURRENT_BINARY_DIR}/org.udpscope.app.desktop -DICON=${UDPSCOPE_ICON_DIR}/scalable/apps/org.udpscope.app.svg -DSYMBOLIC=${UDPSCOPE_ICON_DIR}/scalable/apps/org.udpscope.app-symbolic.svg -P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/install_desktop_entry.cmake COMMENT "Installing the udpscope desktop entry into XDG_DATA_HOME" VERBATIM) add_dependencies(desktop-entry udpscope)