Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
284 changes: 158 additions & 126 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,29 @@ set(CLANG_FORMAT_EXCLUDE_PATTERNS "build/")

find_package(hidapi REQUIRED)

# ------------------------------------------------------------------------------
# Top Level Project Guard
# ------------------------------------------------------------------------------

if(CMAKE_VERSION VERSION_LESS "3.21" OR NOT DEFINED PROJECT_IS_TOP_LEVEL) # PROJECT_IS_TOP_LEVEL is available for CMake 3.21 or later.
# Fallback: check if the path to the top level of the source tree is equal to the current project source dir.
if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR)
set(PROJECT_IS_TOP_LEVEL ON)
else()
set(PROJECT_IS_TOP_LEVEL OFF)
endif()
endif()

# ------------------------------------------------------------------------------
# Subdirectories
# ------------------------------------------------------------------------------

add_subdirectory(lib)
add_subdirectory(cli)
add_subdirectory(tests)

if(PROJECT_IS_TOP_LEVEL)
add_subdirectory(tests)
endif()

# ------------------------------------------------------------------------------
# C++ Standard
Expand Down Expand Up @@ -99,7 +115,7 @@ configure_file(
# Clang format
# ------------------------------------------------------------------------------

if(ENABLE_CLANG_FORMAT)
if(ENABLE_CLANG_FORMAT AND PROJECT_IS_TOP_LEVEL)

# Try to find clang-format-18 first to match CI version
find_program(CLANG_FORMAT_BIN
Expand Down Expand Up @@ -153,7 +169,7 @@ endif()
# Clang Tidy
# ------------------------------------------------------------------------------

if(ENABLE_CLANG_TIDY)
if(ENABLE_CLANG_TIDY AND PROJECT_IS_TOP_LEVEL)

find_program(CLANG_TIDY_BIN NAMES clang-tidy-9 clang-tidy)
find_program(RUN_CLANG_TIDY_BIN NAMES run-clang-tidy-9.py run-clang-tidy.py)
Expand Down Expand Up @@ -250,159 +266,175 @@ endif()
# CLI Executable
# ------------------------------------------------------------------------------

# Create the CLI executable that links against the library
# On Windows, include the resource file for the application icon
if(WIN32)
set(WIN_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.rc)
# Ensure resource compiler can find the icon file
set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/assets")
endif()
if(PROJECT_IS_TOP_LEVEL)

add_executable(headsetcontrol ${CLI_SOURCES} ${WIN_RESOURCES})
target_link_libraries(headsetcontrol PRIVATE headsetcontrol_lib)
target_include_directories(headsetcontrol PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli)
# Create the CLI executable that links against the library
# On Windows, include the resource file for the application icon
if(WIN32)
set(WIN_RESOURCES ${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.rc)
# Ensure resource compiler can find the icon file
set(CMAKE_RC_FLAGS "${CMAKE_RC_FLAGS} -I${CMAKE_CURRENT_SOURCE_DIR}/assets")
endif()

# On MSVC, we need getopt from vcpkg
if(MSVC)
find_package(getopt CONFIG REQUIRED)
target_link_libraries(headsetcontrol PRIVATE $<IF:$<TARGET_EXISTS:getopt::getopt_shared>,getopt::getopt_shared,getopt::getopt_static>)
endif()
add_executable(headsetcontrol ${CLI_SOURCES} ${WIN_RESOURCES})
target_link_libraries(headsetcontrol PRIVATE headsetcontrol_lib)
target_include_directories(headsetcontrol PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli)

# ------------------------------------------------------------------------------
# Installation
# ------------------------------------------------------------------------------
# On MSVC, we need getopt from vcpkg
if(MSVC)
find_package(getopt CONFIG REQUIRED)
target_link_libraries(headsetcontrol PRIVATE $<IF:$<TARGET_EXISTS:getopt::getopt_shared>,getopt::getopt_shared,getopt::getopt_static>)
endif()

install(TARGETS headsetcontrol DESTINATION bin)
install(TARGETS headsetcontrol_lib DESTINATION lib)
# ------------------------------------------------------------------------------
# Installation
# ------------------------------------------------------------------------------

# Install shared library if built
if(BUILD_SHARED_LIBRARY)
install(TARGETS headsetcontrol_shared DESTINATION lib)
endif()
install(TARGETS headsetcontrol DESTINATION bin)
install(TARGETS headsetcontrol_lib DESTINATION lib)

# Install public headers
install(FILES
${CMAKE_CURRENT_SOURCE_DIR}/lib/headsetcontrol.hpp
${CMAKE_CURRENT_SOURCE_DIR}/lib/headsetcontrol_c.h
${CMAKE_CURRENT_SOURCE_DIR}/lib/device.hpp
${CMAKE_CURRENT_SOURCE_DIR}/lib/result_types.hpp
DESTINATION include/headsetcontrol
)
# Install shared library if built
if(BUILD_SHARED_LIBRARY)
install(TARGETS headsetcontrol_shared DESTINATION lib)
endif()

# Install public headers
install(FILES
${CMAKE_CURRENT_SOURCE_DIR}/lib/headsetcontrol.hpp
${CMAKE_CURRENT_SOURCE_DIR}/lib/headsetcontrol_c.h
${CMAKE_CURRENT_SOURCE_DIR}/lib/device.hpp
${CMAKE_CURRENT_SOURCE_DIR}/lib/result_types.hpp
DESTINATION include/headsetcontrol
)

# install udev files on linux
if(UNIX AND NOT APPLE AND NOT ${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD")
set(rules_file 70-headsets.rules)
set(udev_rules_dir lib/udev/rules.d/
CACHE PATH "Path to the directory where udev rules should be installed")
add_custom_command(
OUTPUT ${rules_file}
COMMAND headsetcontrol -u > ${rules_file}
DEPENDS headsetcontrol)
add_custom_target(udevrules ALL DEPENDS ${rules_file})
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/${rules_file}
DESTINATION ${udev_rules_dir})
endif()

# install udev files on linux
if(UNIX AND NOT APPLE AND NOT ${CMAKE_HOST_SYSTEM_NAME} MATCHES "FreeBSD")
set(rules_file 70-headsets.rules)
set(udev_rules_dir lib/udev/rules.d/
CACHE PATH "Path to the directory where udev rules should be installed")
add_custom_command(
OUTPUT ${rules_file}
COMMAND headsetcontrol -u > ${rules_file}
DEPENDS headsetcontrol)
add_custom_target(udevrules ALL DEPENDS ${rules_file})
install(
FILES ${CMAKE_CURRENT_BINARY_DIR}/${rules_file}
DESTINATION ${udev_rules_dir})
endif()

# ------------------------------------------------------------------------------
# Testing
# ------------------------------------------------------------------------------

include(CTest)
enable_testing()
if(PROJECT_IS_TOP_LEVEL)

# Integration Test: Basic application run
add_test(NAME integration_basic_run
COMMAND headsetcontrol
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set_tests_properties(integration_basic_run
PROPERTIES PASS_REGULAR_EXPRESSION "No supported device found;Found")
include(CTest)
enable_testing()

# Test targets
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
DEPENDS headsetcontrol)
# Integration Test: Basic application run
add_test(NAME integration_basic_run
COMMAND headsetcontrol
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
set_tests_properties(integration_basic_run
PROPERTIES PASS_REGULAR_EXPRESSION "No supported device found;Found")

add_custom_target(test-verbose
COMMAND ${CMAKE_CTEST_COMMAND} --verbose --output-on-failure
DEPENDS headsetcontrol
COMMENT "Running tests with verbose output"
)
# Test targets
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure
DEPENDS headsetcontrol)

add_custom_target(test-verbose
COMMAND ${CMAKE_CTEST_COMMAND} --verbose --output-on-failure
DEPENDS headsetcontrol
COMMENT "Running tests with verbose output"
)

endif()

# ------------------------------------------------------------------------------
# Unit Tests
# ------------------------------------------------------------------------------

option(BUILD_UNIT_TESTS "Build unit tests with mock HID interface" ON)
if(PROJECT_IS_TOP_LEVEL)

option(BUILD_UNIT_TESTS "Build unit tests with mock HID interface" ON)

if(BUILD_UNIT_TESTS)
add_executable(headsetcontrol_tests ${TEST_SOURCES})
target_link_libraries(headsetcontrol_tests PRIVATE headsetcontrol_lib)
target_include_directories(headsetcontrol_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli)

if(BUILD_UNIT_TESTS)
add_executable(headsetcontrol_tests ${TEST_SOURCES})
target_link_libraries(headsetcontrol_tests PRIVATE headsetcontrol_lib)
target_include_directories(headsetcontrol_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/cli)
add_test(NAME unit_mock_devices
COMMAND headsetcontrol_tests
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

add_test(NAME unit_mock_devices
COMMAND headsetcontrol_tests
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
add_dependencies(check headsetcontrol_tests)
endif()

add_dependencies(check headsetcontrol_tests)
endif()

# ------------------------------------------------------------------------------
# CPack Package Generation
# ------------------------------------------------------------------------------

set(CPACK_PACKAGE_NAME "headsetcontrol")
set(CPACK_PACKAGE_VENDOR "Sapd")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Control USB headsets on Linux, macOS, and Windows")
set(CPACK_PACKAGE_DESCRIPTION "HeadsetControl is a tool to control USB-connected headsets. \
It supports setting sidetone, LED lights, equalizer, inactive time, and more for various \
gaming headsets from Logitech, SteelSeries, Corsair, HyperX, Roccat, and Audeze.")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/Sapd/HeadsetControl")
set(CPACK_PACKAGE_CONTACT "https://github.com/Sapd/HeadsetControl")
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
endif()
if(PROJECT_IS_TOP_LEVEL)

set(CPACK_PACKAGE_NAME "headsetcontrol")
set(CPACK_PACKAGE_VENDOR "Sapd")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Control USB headsets on Linux, macOS, and Windows")
set(CPACK_PACKAGE_DESCRIPTION "HeadsetControl is a tool to control USB-connected headsets. \
It supports setting sidetone, LED lights, equalizer, inactive time, and more for various \
gaming headsets from Logitech, SteelSeries, Corsair, HyperX, Roccat, and Audeze.")
set(CPACK_PACKAGE_HOMEPAGE_URL "https://github.com/Sapd/HeadsetControl")
set(CPACK_PACKAGE_CONTACT "https://github.com/Sapd/HeadsetControl")
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
endif()

# Extract version components from GIT_VERSION (e.g., "2.7.0" or "2.7.0-123-gabcdef")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" VERSION_MATCH "${GIT_VERSION}")
if(VERSION_MATCH)
set(CPACK_PACKAGE_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(CPACK_PACKAGE_VERSION_MINOR "${CMAKE_MATCH_2}")
set(CPACK_PACKAGE_VERSION_PATCH "${CMAKE_MATCH_3}")
set(CPACK_PACKAGE_VERSION "${GIT_VERSION}")
else()
# Debian/RPM versions must start with a digit
# If git version doesn't (e.g., "continuous-12-g91b4f06"), prefix with 0.0.0~
set(CPACK_PACKAGE_VERSION_MAJOR "0")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PACKAGE_VERSION "0.0.0~${GIT_VERSION}")
endif()
# Extract version components from GIT_VERSION (e.g., "2.7.0" or "2.7.0-123-gabcdef")
string(REGEX MATCH "^([0-9]+)\\.([0-9]+)\\.([0-9]+)" VERSION_MATCH "${GIT_VERSION}")
if(VERSION_MATCH)
set(CPACK_PACKAGE_VERSION_MAJOR "${CMAKE_MATCH_1}")
set(CPACK_PACKAGE_VERSION_MINOR "${CMAKE_MATCH_2}")
set(CPACK_PACKAGE_VERSION_PATCH "${CMAKE_MATCH_3}")
set(CPACK_PACKAGE_VERSION "${GIT_VERSION}")
else()
# Debian/RPM versions must start with a digit
# If git version doesn't (e.g., "continuous-12-g91b4f06"), prefix with 0.0.0~
set(CPACK_PACKAGE_VERSION_MAJOR "0")
set(CPACK_PACKAGE_VERSION_MINOR "0")
set(CPACK_PACKAGE_VERSION_PATCH "0")
set(CPACK_PACKAGE_VERSION "0.0.0~${GIT_VERSION}")
endif()

# Debian package settings
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Denis Arnst <git@sapd.eu>")
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libhidapi-hidraw0 | libhidapi-libusb0")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/Sapd/HeadsetControl")
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)

# RPM package settings
set(CPACK_RPM_PACKAGE_LICENSE "GPLv3")
set(CPACK_RPM_PACKAGE_GROUP "Applications/System")
set(CPACK_RPM_PACKAGE_REQUIRES "hidapi")
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)

# NSIS (Windows installer) settings
set(CPACK_NSIS_DISPLAY_NAME "HeadsetControl")
set(CPACK_NSIS_PACKAGE_NAME "HeadsetControl")
set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/Sapd/HeadsetControl")
set(CPACK_NSIS_HELP_LINK "https://github.com/Sapd/HeadsetControl/wiki")
set(CPACK_NSIS_CONTACT "git@sapd.eu")
set(CPACK_NSIS_MODIFY_PATH ON)
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico")
set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico")
set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico")
endif()
# Debian package settings
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "Denis Arnst <git@sapd.eu>")
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libhidapi-hidraw0 | libhidapi-libusb0")
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "https://github.com/Sapd/HeadsetControl")
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)

# RPM package settings
set(CPACK_RPM_PACKAGE_LICENSE "GPLv3")
set(CPACK_RPM_PACKAGE_GROUP "Applications/System")
set(CPACK_RPM_PACKAGE_REQUIRES "hidapi")
set(CPACK_RPM_FILE_NAME RPM-DEFAULT)

# NSIS (Windows installer) settings
set(CPACK_NSIS_DISPLAY_NAME "HeadsetControl")
set(CPACK_NSIS_PACKAGE_NAME "HeadsetControl")
set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/Sapd/HeadsetControl")
set(CPACK_NSIS_HELP_LINK "https://github.com/Sapd/HeadsetControl/wiki")
set(CPACK_NSIS_CONTACT "git@sapd.eu")
set(CPACK_NSIS_MODIFY_PATH ON)
set(CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico")
set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico")
set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/assets/headsetcontrol.ico")
endif()

include(CPack)
include(CPack)

endif()