cmake_minimum_required(VERSION 3.20) project(impress_voice_input VERSION 0.1.0 LANGUAGES CXX DESCRIPTION "基于 ONNX 的实时语音转文本输入法" ) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) # 默认使用 Release 带调试信息 if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Build type" FORCE) set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "RelWithDebInfo" "MinSizeRel") endif() set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) # ============================================================================ # 依赖查找 # ============================================================================ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") # Qt 6 find_package(Qt6 REQUIRED COMPONENTS Core Widgets Concurrent Network) # Windows 不需要 D-Bus,Linux/macOS 按需加载 if(WIN32) find_package(Qt6 REQUIRED COMPONENTS Gui) else() find_package(Qt6 REQUIRED COMPONENTS DBus) endif() qt_standard_project_setup() # ONNX Runtime include(dependencies) # ============================================================================ # 源文件 # ============================================================================ set(SOURCES src/main.cpp # App src/app/application.cpp src/app/config_manager.cpp # Core (平台无关) src/core/stt_engine.cpp src/core/sense_voice_engine.cpp src/core/ort_minimal.cpp src/core/sense_voice_features.cpp src/core/sense_voice_tokenizer.cpp src/core/mel_spectrogram.cpp src/core/whisper_tokenizer.cpp src/core/audio_processor.cpp src/core/vad.cpp src/core/voice_input_service.cpp # Audio src/audio/audio_capture.cpp src/audio/audio_decoder.cpp src/audio/audio_ring_buffer.cpp src/audio/streaming_audio_writer.cpp # UI src/ui/main_window.cpp src/ui/stt_test_page.cpp src/ui/file_transcribe_page.cpp src/ui/settings_page.cpp src/ui/widgets/audio_waveform.cpp src/ui/widgets/text_output.cpp src/ui/widgets/progress_panel.cpp src/ui/widgets/hotkey_recorder.cpp src/ui/widgets/recording_indicator.cpp # Utils src/utils/logger.cpp src/utils/timer.cpp src/utils/string_utils.cpp ) set(HEADERS src/app/application.h src/app/config_manager.h src/core/stt_engine.h src/core/sense_voice_engine.h src/core/sense_voice_features.h src/core/sense_voice_tokenizer.h src/core/sense_voice_cmvn.h src/core/mel_spectrogram.h src/core/whisper_tokenizer.h src/core/audio_processor.h src/core/vad.h src/core/voice_input_service.h src/audio/audio_capture.h src/audio/audio_decoder.h src/audio/audio_ring_buffer.h src/audio/streaming_audio_writer.h src/ui/main_window.h src/ui/stt_test_page.h src/ui/file_transcribe_page.h src/ui/settings_page.h src/ui/widgets/audio_waveform.h src/ui/widgets/text_output.h src/ui/widgets/progress_panel.h src/ui/widgets/hotkey_recorder.h src/ui/widgets/recording_indicator.h src/utils/logger.h src/utils/timer.h src/utils/string_utils.h ) # ============================================================================ # 平台特定源文件 # ============================================================================ if(WIN32) # Windows 实现(RegisterHotKey + SendInput) list(APPEND SOURCES src/core/win_hotkey.cpp src/core/win_text_injector.cpp) list(APPEND HEADERS src/core/win_hotkey.h src/core/win_text_injector.h) add_compile_definitions(PLATFORM_WINDOWS) elseif(APPLE) # macOS 实现(待完善:Carbon EventManager / CGEvent) list(APPEND SOURCES src/core/mac_hotkey.cpp src/core/mac_text_injector.cpp) list(APPEND HEADERS src/core/mac_hotkey.h src/core/mac_text_injector.h) add_compile_definitions(PLATFORM_MACOS) else() # Linux 实现(X11 XGrabKey / evdev / libei / D-Bus Portal) list(APPEND SOURCES src/core/caps_lock_voice_hotkey.cpp src/core/caps_lock_backend.cpp src/core/caps_lock_xcb.cpp src/core/caps_lock_evdev.cpp src/core/wayland_text_injector.cpp ) list(APPEND HEADERS src/core/caps_lock_voice_hotkey.h src/core/caps_lock_backend.h src/core/caps_lock_xcb.h src/core/caps_lock_evdev.h src/core/wayland_text_injector.h ) add_compile_definitions(PLATFORM_LINUX) endif() # Windows 使用 WIN32 标志隐藏启动控制台,并嵌入应用图标 if(WIN32) add_executable(${PROJECT_NAME} WIN32 ${SOURCES} ${HEADERS} src/ui/resources/styles/styles.qrc src/ui/resources/icons/icon.rc ) else() add_executable(${PROJECT_NAME} ${SOURCES} ${HEADERS} src/ui/resources/styles/styles.qrc ) endif() target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/third_party ${ONNXRUNTIME_INCLUDE_DIRS} ${PORTAUDIO_INCLUDE_DIRS} ${DR_LIBS_INCLUDE_DIR} ${NLOHMANN_JSON_INCLUDE_DIR} ) target_link_libraries(${PROJECT_NAME} PRIVATE Qt6::Core Qt6::Widgets Qt6::Concurrent Qt6::Network $<$:Qt6::Gui> $<$>:Qt6::DBus> ${ONNXRUNTIME_LIBRARIES} ${PORTAUDIO_LIBRARIES} $<$>:pthread> ) target_compile_options(${PROJECT_NAME} PRIVATE $<$:-Wall -Wextra -Wpedantic> $<$:/W4> ) # ============================================================================ # Linux: libei 全局快捷键(GNOME 47+) # ============================================================================ if(NOT WIN32 AND NOT APPLE) find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules(LIBEI QUIET libei-1.0) endif() if(LIBEI_FOUND) find_library(LIBEI_LIB_PATH NAMES ei PATHS /usr/lib64 /usr/lib /usr/local/lib ) if(LIBEI_LIB_PATH) message(STATUS "找到 libei: ${LIBEI_LIB_PATH} (${LIBEI_VERSION})") target_include_directories(${PROJECT_NAME} PRIVATE ${LIBEI_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME} PRIVATE ${LIBEI_LIB_PATH}) add_compile_definitions(HAVE_LIBEI) endif() endif() endif() # ============================================================================ # Linux: X11 全局快捷键(XCB XGrabKey) # ============================================================================ if(NOT WIN32 AND NOT APPLE) find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules(XCB QUIET xcb) pkg_check_modules(X11 QUIET x11) endif() if(XCB_FOUND AND X11_FOUND) message(STATUS "找到 XCB: ${XCB_LIBDIR}") message(STATUS "找到 X11: ${X11_LIBDIR}") target_include_directories(${PROJECT_NAME} PRIVATE ${XCB_INCLUDE_DIRS} ${X11_INCLUDE_DIRS}) target_link_libraries(${PROJECT_NAME} PRIVATE ${XCB_LIBRARIES} ${X11_LIBRARIES}) add_compile_definitions(HAVE_XCB) else() message(STATUS "XCB/X11 未找到,X11 全局快捷键不可用") endif() endif() # ============================================================================ # Windows DLL 复制(交叉编译时自动拷贝到输出目录) # ============================================================================ if(WIN32) if(ONNXRUNTIME_DLL) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ONNXRUNTIME_DLL}" $ COMMENT "拷贝 onnxruntime.dll 到输出目录" ) endif() if(ONNXRUNTIME_PROVIDERS_DLL) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${ONNXRUNTIME_PROVIDERS_DLL}" $ COMMENT "拷贝 onnxruntime_providers_shared.dll 到输出目录" ) endif() if(PORTAUDIO_DLL) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different "${PORTAUDIO_DLL}" $ COMMENT "拷贝 libportaudio.dll 到输出目录" ) endif() endif() # ============================================================================ # 安装 # ============================================================================ install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION bin BUNDLE DESTINATION . ) # GNOME Shell 扩展 if(NOT WIN32 AND NOT APPLE) set(GNOME_EXT_UUID "io.impress.voice-input-hotkey@impress") set(GNOME_EXT_SRC "${CMAKE_CURRENT_SOURCE_DIR}/gnome-extension/${GNOME_EXT_UUID}") set(GNOME_EXT_DST "${CMAKE_INSTALL_PREFIX}/share/gnome-shell/extensions/${GNOME_EXT_UUID}") install(FILES "${GNOME_EXT_SRC}/extension.js" "${GNOME_EXT_SRC}/metadata.json" DESTINATION share/gnome-shell/extensions/${GNOME_EXT_UUID}) endif() # ============================================================================ # 测试 # ============================================================================ option(BUILD_TESTS "构建单元测试" OFF) if(BUILD_TESTS) enable_testing() add_subdirectory(tests) endif()