cmake_minimum_required(VERSION 3.16) project(cst-agent-app LANGUAGES C CXX VERSION 0.1.0) # The Qt front end (app/) embeds the C engine in-process: it links libcstcore # and never opens a port. Two targets: # # cst-app-selftest pure C smoke test of the embedding path (no Qt), so the # C side is still guarded on machines without Qt Quick # cst-agent-app the GUI (Qt 6 Quick + QML), built when the Qt modules # are there # # Standalone build (builds the engine from ../ too): # cmake -S app -B build-app -DCMAKE_BUILD_TYPE=Debug && cmake --build build-app -j # Inside the repo build (the parent defines the cstcore target, shared with the CLI): # cmake -B build -DCST_BUILD_APP=ON && cmake --build build --target cst-agent-app # # Desktop install (the window, its launcher entry and the icon; the CLI has # its own installer) -- default.nix installs exactly this component: # cmake --install build --component app --prefix ~/.local # # Extra QML import paths, e.g. a Kirigami installed away from the Qt prefix: # -DCST_APP_QML_IMPORT=... (added to QML2_IMPORT_PATH / the app's runtime) option(CST_APP_GUI "build the Qt GUI (needs Qt6 Quick, Qml, QuickControls2)" ON) option(CST_APP_SELFTEST "build the C embedding smoke test" ON) # --- the engine ---------------------------------------------------------- # Used as a subproject (../CMakeLists.txt already defined cstcore, engine # compiled once for CLI and app) or built here from the sources next door. if(NOT TARGET cstcore) set(CST_APP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/..") include_directories("${CST_APP_ROOT}/include" "${CST_APP_ROOT}/vendor/cjson") file(GLOB CST_APP_CORE_SRC "${CST_APP_ROOT}/src/*.c" "${CST_APP_ROOT}/src/tools/*.c" "${CST_APP_ROOT}/vendor/cjson/cJSON.c") list(REMOVE_ITEM CST_APP_CORE_SRC "${CST_APP_ROOT}/src/main.c" "${CST_APP_ROOT}/src/tui.c" "${CST_APP_ROOT}/src/tui_stub.c" "${CST_APP_ROOT}/src/textsel.c") # The HTTP front end is not part of an in-process app: no server, no port. list(REMOVE_ITEM CST_APP_CORE_SRC "${CST_APP_ROOT}/src/net.c" "${CST_APP_ROOT}/src/http_srv.c" "${CST_APP_ROOT}/src/webui.c") # web_md.c stays in: the window renders markdown with it (AgentCore:: # renderMarkdown) and the md_to_html tool needs it. Only the HTTP server # is left out. if(EXISTS "${CST_APP_ROOT}/.gen/models_builtin.inc") include_directories("${CST_APP_ROOT}/.gen") endif() add_library(cstcore STATIC ${CST_APP_CORE_SRC}) target_compile_definitions(cstcore PRIVATE CST_NO_WEBUI=1) find_package(CURL REQUIRED) find_package(Threads REQUIRED) target_link_libraries(cstcore PUBLIC ${CURL_LIBRARIES} Threads::Threads m) message(STATUS "app: building the engine from ${CST_APP_ROOT}") else() message(STATUS "app: linking the cstcore target of the parent build") endif() # --- the C smoke test ---------------------------------------------------- if(CST_APP_SELFTEST) add_executable(cst-app-selftest tools/selftest.c) target_link_libraries(cst-app-selftest PRIVATE cstcore) enable_testing() add_test(NAME cst-app-selftest COMMAND cst-app-selftest) endif() # --- the GUI ------------------------------------------------------------- if(CST_APP_GUI) # Android's platform file talks to the platform through QtAndroidPrivate # (the POST_NOTIFICATIONS probe), whose include path comes with CorePrivate. if(ANDROID) find_package(Qt6 6.4 COMPONENTS Core Gui Qml Quick QuickControls2 Test CorePrivate QUIET) else() find_package(Qt6 6.4 COMPONENTS Core Gui Qml Quick QuickControls2 Test QUIET) endif() if(NOT Qt6_FOUND) message(WARNING "app: Qt 6 Quick/Qml not found, skipping cst-agent-app " "(the C selftest still builds; install e.g. qt6.qtdeclarative / " "qt6.qtquickcontrols2 and reconfigure)") else() qt_standard_project_setup() # Qt 6.5+ policies: keep the module resource at :/qt/qml// (what # main.cpp loads) and let CMake generate the qmldir of qml/ for us. qt_policy(SET QTP0001 NEW) qt_policy(SET QTP0004 NEW) # The front end's platform tools: one file per platform, the same split # core/ToolProvider.cpp makes. Android brings its own (JNI, owner # "app:android"), Windows its own (PowerShell popup, owner "app:windows"); # the rest use the Linux file. if(ANDROID) set(CST_APP_PLATFORM_SRC src/platform/android/AndroidTools.cpp) elseif(WIN32) set(CST_APP_PLATFORM_SRC src/platform/windows/NotifyTool.cpp) else() set(CST_APP_PLATFORM_SRC src/platform/linux/NotifyTool.cpp) endif() qt_add_executable(cst-agent-app src/main.cpp src/core/AgentCore.cpp src/core/ToolProvider.cpp src/core/PdfTools.cpp src/ui/UiTest.cpp src/ui/ThemeIcons.cpp src/ui/SessionImages.cpp src/ui/BackKey.cpp ${CST_APP_PLATFORM_SRC}) target_include_directories(cst-agent-app PRIVATE src "${CMAKE_CURRENT_SOURCE_DIR}/../include") target_compile_definitions(cst-agent-app PRIVATE CST_APP_VERSION="${PROJECT_VERSION}") if(CST_APP_QML_IMPORT) target_compile_definitions(cst-agent-app PRIVATE CST_APP_QML_IMPORT="${CST_APP_QML_IMPORT}") endif() target_link_libraries(cst-agent-app PRIVATE Qt6::Core Qt6::Gui Qt6::Qml Qt6::Quick Qt6::QuickControls2 Qt6::Test cstcore) if(ANDROID) # app/src/platform/android/AndroidTools.cpp talks to the platform # through QtAndroidPrivate (permissions) -- a private header, hence the # private module for its include path. target_link_libraries(cst-agent-app PRIVATE Qt6::CorePrivate) endif() if(WIN32) set_target_properties(cst-agent-app PROPERTIES WIN32_EXECUTABLE TRUE) endif() # A desktop install. The icon goes where the theme layout says, so the # entry's Icon= name finds it, and the entry's file name is the app_id Qt # sets (QGuiApplication::applicationName) -- rename one and the launcher # loses the icon. All of it in the "app" component: the CLI keeps coming # from scripts/bootstrap_linux.sh, and two `cst-agent` in PATH is how a # version nobody meant to run gets run. if(NOT ANDROID) include(GNUInstallDirs) install(TARGETS cst-agent-app RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT app) install(FILES linux/cst-agent-app.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications COMPONENT app) install(FILES linux/cst-agent-app.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps COMPONENT app) endif() # --- Android (APK) ----------------------------------------------------- # The engine is compiled in (top-level CMakeLists: -DCST_BUILD_TUI=OFF, # -DCST_BUILD_WEBUI=OFF, so src/webui_stub.c stands in for the server and # nothing listens on a port). The manifest, icons and strings live in # app/android/, which androiddeployqt copies next to its own gradle # project. minSdk 24 is the engine's own API_LEVEL (scripts/ # build_third_party_android.sh builds curl/mbedTLS for android-24). if(ANDROID) file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/../include/cst_version.h" _cst_ver_lines REGEX "#define CST_VERSION_(MAJOR|MINOR|PATCH) ") foreach(_line ${_cst_ver_lines}) if(_line MATCHES "CST_VERSION_MAJOR ([0-9]+)") set(_cst_vmaj "${CMAKE_MATCH_1}") elseif(_line MATCHES "CST_VERSION_MINOR ([0-9]+)") set(_cst_vmin "${CMAKE_MATCH_1}") elseif(_line MATCHES "CST_VERSION_PATCH ([0-9]+)") set(_cst_vpat "${CMAKE_MATCH_1}") endif() endforeach() set_target_properties(cst-agent-app PROPERTIES QT_ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android" QT_ANDROID_MIN_SDK_VERSION 24 QT_ANDROID_TARGET_SDK_VERSION 35 QT_ANDROID_VERSION_NAME "${_cst_vmaj}.${_cst_vmin}.${_cst_vpat}" QT_ANDROID_VERSION_CODE 1 QT_ANDROID_ABIS "${ANDROID_ABI}") message(STATUS "app: Android package " "${_cst_vmaj}.${_cst_vmin}.${_cst_vpat}, ABI ${ANDROID_ABI}, " "minSdk 24, targetSdk 35") # Signing: androiddeployqt signs the APK itself and refuses a signed # gradle output ("Could not copy unsigned APK"), so the keystore goes # here, not into gradle. The path, alias and passwords arrive through # the QT_ANDROID_KEYSTORE_* environment variables; # scripts/build_apk_android.sh points them at the standard Android debug # key it creates on first use, and CST_ANDROID_KEYSTORE overrides it. if(DEFINED ENV{QT_ANDROID_KEYSTORE_PATH} AND NOT "$ENV{QT_ANDROID_KEYSTORE_PATH}" STREQUAL "") set(QT_ANDROID_SIGN_APK TRUE) message(STATUS "app: signing the APK with " "$ENV{QT_ANDROID_KEYSTORE_PATH} ($ENV{QT_ANDROID_KEYSTORE_ALIAS})") else() message(STATUS "app: QT_ANDROID_KEYSTORE_PATH unset -- the APK stays " "unsigned and 'adb install' will refuse it (INSTALL_PARSE_FAILED_NO_CERTIFICATES)") endif() endif() # Theme is read by every component, so it is a QML singleton (one instance, # no object needed in the tree). set_source_files_properties(qml/Theme.qml PROPERTIES QT_QML_SINGLETON_TYPE TRUE) # Keep the module flat in the resource tree (:qt/qml/CstAgent/Main.qml). # Sources live in qml/, but a module whose files sit in a subdirectory of # the URI makes Qt register the types with relative URLs ("qmlRegisterType # requires absolute URLs" on every start). foreach(_qml Main ChatView Sidebar SessionRow MessageCard ToolCard ThinkBlock NoteLine TodoPanel Composer ConsentDialog AskDialog SettingsDialog TaskPanel ThemeIcon Theme ImageStrip) set_source_files_properties(qml/${_qml}.qml PROPERTIES QT_RESOURCE_ALIAS ${_qml}.qml) endforeach() qt_add_qml_module(cst-agent-app URI CstAgent VERSION 1.0 QML_FILES qml/Main.qml qml/ChatView.qml qml/Sidebar.qml qml/SessionRow.qml qml/MessageCard.qml qml/ToolCard.qml qml/ThinkBlock.qml qml/NoteLine.qml qml/TodoPanel.qml qml/Composer.qml qml/TaskPanel.qml qml/ConsentDialog.qml qml/AskDialog.qml qml/SettingsDialog.qml qml/ThemeIcon.qml qml/ImageStrip.qml qml/Theme.qml) # The icons the window asks for, compiled in (app/icons, Breeze's scalable # names copied at 24@3x). The desktop finds them in the platform's icon # theme and only falls back to these (ThemeIcons.cpp); a machine without # any theme -- Android -- has nothing else, and before this the QML glyph # fallback was all that showed, which the phone's fonts cannot render. set(CST_APP_ICONS go-down mail-attachment application-menu settings-configure edit-copy edit-delete arrow-down arrow-right list-add task-complete box document-edit text-x-generic folder-open edit-find code-context internet-services format-list-unordered help-hint) set(_cst_icon_files "") foreach(_icon ${CST_APP_ICONS}) list(APPEND _cst_icon_files "${CMAKE_CURRENT_SOURCE_DIR}/icons/${_icon}.svg") endforeach() qt_add_resources(cst-agent-app "cst_icons" PREFIX "/cst-icons" BASE "${CMAKE_CURRENT_SOURCE_DIR}/icons" FILES ${_cst_icon_files}) endif() endif()