Add Alvin library: input, capture, windows and vision
A cross-platform C++23 library with a platform-neutral public API and runtime-selected backends: - Input: Wayland virtual pointer/keyboard (Hyprland, wlroots), libei via the RemoteDesktop portal (GNOME, KDE), uinput, and Win32 SendInput - Capture: ScreenCast portal + PipeWire, and Win32 GDI - Windows: Hyprland IPC and Win32 enumeration - Monitors via wl_output/xdg-output and EnumDisplayMonitors - Optional OpenCV template matching (alvin::vision) Also adds the `alvin` CLI, Catch2 unit tests, install rules with find_package(Alvin) support, API docs (docs/API.md and the Obsidian vault), and README instructions for both ways of consuming the library. Windows backends are written but not yet compiled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
76ff506fd0
commit
ca7e62ac8d
59 files changed
+6515
-1
No files matched your search
+259
@@ -0,0 +1,259 @@
|
||||
cmake_minimum_required(VERSION 3.24)
|
||||
|
||||
project(Alvin
|
||||
VERSION 0.1.0
|
||||
DESCRIPTION "Agent Leveraging Vision and Input: cross-platform input synthesis and screen capture"
|
||||
LANGUAGES C CXX) # C only for wayland-scanner output
|
||||
|
||||
set(CMAKE_CXX_STANDARD 23)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_CXX_EXTENSIONS OFF)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Options. "AUTO" enables a backend when its dependencies are found.
|
||||
# ---------------------------------------------------------------------------
|
||||
set(ALVIN_WITH_WAYLAND AUTO CACHE STRING "Wayland virtual pointer/keyboard input + monitor listing (ON/OFF/AUTO)")
|
||||
set(ALVIN_WITH_LIBEI AUTO CACHE STRING "libei input via the RemoteDesktop portal (ON/OFF/AUTO)")
|
||||
set(ALVIN_WITH_PORTAL AUTO CACHE STRING "ScreenCast portal + PipeWire capture (ON/OFF/AUTO)")
|
||||
set(ALVIN_WITH_UINPUT ON CACHE BOOL "/dev/uinput input backend")
|
||||
set(ALVIN_WITH_VISION AUTO CACHE STRING "OpenCV-based alvin::vision library (ON/OFF/AUTO)")
|
||||
option(ALVIN_BUILD_EXAMPLES "Build the alvin command-line tool" ON)
|
||||
option(ALVIN_BUILD_TESTS "Build unit tests" ${PROJECT_IS_TOP_LEVEL})
|
||||
|
||||
# Resolves an AUTO/ON/OFF option against whether its dependencies were found.
|
||||
function(alvin_resolve option found)
|
||||
if(${option} STREQUAL "AUTO")
|
||||
set(${option}_ENABLED ${found} PARENT_SCOPE)
|
||||
elseif(${option})
|
||||
if(NOT ${found})
|
||||
message(FATAL_ERROR "${option}=ON but its dependencies were not found")
|
||||
endif()
|
||||
set(${option}_ENABLED TRUE PARENT_SCOPE)
|
||||
else()
|
||||
set(${option}_ENABLED FALSE PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
add_library(alvin
|
||||
src/common/core.cpp
|
||||
src/common/frame.cpp
|
||||
src/common/json.cpp
|
||||
src/common/keys.cpp
|
||||
)
|
||||
add_library(alvin::alvin ALIAS alvin)
|
||||
target_include_directories(alvin
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
|
||||
PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
target_compile_features(alvin PUBLIC cxx_std_23)
|
||||
if(MSVC)
|
||||
target_compile_options(alvin PRIVATE /W4 /utf-8)
|
||||
else()
|
||||
target_compile_options(alvin PRIVATE -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# No export macros in the headers; let CMake export every symbol from the DLL.
|
||||
set_target_properties(alvin PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
|
||||
set(ALVIN_ENABLED_BACKENDS "")
|
||||
|
||||
# Links a pkg-config dependency privately and records how AlvinConfig.cmake can
|
||||
# find it again: a static libalvin carries no record of its own dependencies.
|
||||
set(ALVIN_CONFIG_DEPENDENCIES "")
|
||||
macro(alvin_private_pkg var)
|
||||
target_link_libraries(alvin PRIVATE PkgConfig::${var})
|
||||
string(APPEND ALVIN_CONFIG_DEPENDENCIES " pkg_check_modules(${var} REQUIRED QUIET IMPORTED_TARGET ${ARGN})\n")
|
||||
endmacro()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Linux
|
||||
# ---------------------------------------------------------------------------
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(XKBCOMMON REQUIRED IMPORTED_TARGET xkbcommon)
|
||||
pkg_check_modules(WAYLAND IMPORTED_TARGET wayland-client)
|
||||
pkg_check_modules(WAYLAND_PROTOCOLS wayland-protocols)
|
||||
find_program(WAYLAND_SCANNER wayland-scanner)
|
||||
pkg_check_modules(GIO IMPORTED_TARGET gio-2.0 gio-unix-2.0)
|
||||
pkg_check_modules(PIPEWIRE IMPORTED_TARGET libpipewire-0.3)
|
||||
pkg_check_modules(LIBEI IMPORTED_TARGET libei-1.0)
|
||||
|
||||
set(_wayland_found FALSE)
|
||||
if(WAYLAND_FOUND AND WAYLAND_PROTOCOLS_FOUND AND WAYLAND_SCANNER)
|
||||
set(_wayland_found TRUE)
|
||||
endif()
|
||||
set(_portal_found FALSE)
|
||||
if(GIO_FOUND AND PIPEWIRE_FOUND)
|
||||
set(_portal_found TRUE)
|
||||
endif()
|
||||
set(_libei_found FALSE)
|
||||
if(GIO_FOUND AND LIBEI_FOUND)
|
||||
set(_libei_found TRUE)
|
||||
endif()
|
||||
alvin_resolve(ALVIN_WITH_WAYLAND _wayland_found)
|
||||
alvin_resolve(ALVIN_WITH_PORTAL _portal_found)
|
||||
alvin_resolve(ALVIN_WITH_LIBEI _libei_found)
|
||||
|
||||
target_sources(alvin PRIVATE
|
||||
src/linux/platform_linux.cpp
|
||||
src/linux/hyprland_windows.cpp
|
||||
src/linux/keycodes.cpp
|
||||
src/linux/xkb.cpp)
|
||||
alvin_private_pkg(XKBCOMMON xkbcommon)
|
||||
|
||||
if(ALVIN_WITH_WAYLAND_ENABLED)
|
||||
pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir)
|
||||
set(_proto_out ${CMAKE_CURRENT_BINARY_DIR}/protocols)
|
||||
file(MAKE_DIRECTORY ${_proto_out})
|
||||
set(_protocols
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/wlr-virtual-pointer-unstable-v1.xml
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/virtual-keyboard-unstable-v1.xml
|
||||
${WAYLAND_PROTOCOLS_DIR}/unstable/xdg-output/xdg-output-unstable-v1.xml)
|
||||
foreach(xml ${_protocols})
|
||||
get_filename_component(name ${xml} NAME_WE)
|
||||
add_custom_command(
|
||||
OUTPUT ${_proto_out}/${name}-client-protocol.h ${_proto_out}/${name}-protocol.c
|
||||
COMMAND ${WAYLAND_SCANNER} client-header ${xml} ${_proto_out}/${name}-client-protocol.h
|
||||
COMMAND ${WAYLAND_SCANNER} private-code ${xml} ${_proto_out}/${name}-protocol.c
|
||||
DEPENDS ${xml}
|
||||
VERBATIM)
|
||||
list(APPEND _proto_sources ${_proto_out}/${name}-protocol.c)
|
||||
list(APPEND _proto_headers ${_proto_out}/${name}-client-protocol.h)
|
||||
endforeach()
|
||||
# wayland-scanner emits C; build it as a small C object library.
|
||||
add_library(alvin_wayland_protocols OBJECT ${_proto_sources} ${_proto_headers})
|
||||
target_include_directories(alvin_wayland_protocols PUBLIC ${_proto_out})
|
||||
target_link_libraries(alvin_wayland_protocols PUBLIC PkgConfig::WAYLAND)
|
||||
set_target_properties(alvin_wayland_protocols PROPERTIES POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
target_sources(alvin PRIVATE
|
||||
src/linux/wayland.cpp
|
||||
src/linux/wlr_input.cpp
|
||||
$<TARGET_OBJECTS:alvin_wayland_protocols>)
|
||||
target_include_directories(alvin PRIVATE ${_proto_out})
|
||||
alvin_private_pkg(WAYLAND wayland-client)
|
||||
add_dependencies(alvin alvin_wayland_protocols)
|
||||
target_compile_definitions(alvin PRIVATE ALVIN_HAVE_WAYLAND)
|
||||
list(APPEND ALVIN_ENABLED_BACKENDS "wlr-input" "wayland-monitors")
|
||||
endif()
|
||||
|
||||
if(ALVIN_WITH_PORTAL_ENABLED OR ALVIN_WITH_LIBEI_ENABLED)
|
||||
target_sources(alvin PRIVATE src/linux/portal.cpp)
|
||||
alvin_private_pkg(GIO gio-2.0 gio-unix-2.0)
|
||||
endif()
|
||||
|
||||
if(ALVIN_WITH_PORTAL_ENABLED)
|
||||
target_sources(alvin PRIVATE src/linux/pipewire_capture.cpp)
|
||||
alvin_private_pkg(PIPEWIRE libpipewire-0.3)
|
||||
target_compile_definitions(alvin PRIVATE ALVIN_HAVE_PORTAL)
|
||||
list(APPEND ALVIN_ENABLED_BACKENDS "portal-capture")
|
||||
endif()
|
||||
|
||||
if(ALVIN_WITH_LIBEI_ENABLED)
|
||||
target_sources(alvin PRIVATE src/linux/libei_input.cpp)
|
||||
alvin_private_pkg(LIBEI libei-1.0)
|
||||
target_compile_definitions(alvin PRIVATE ALVIN_HAVE_LIBEI)
|
||||
if(LIBEI_VERSION VERSION_GREATER_EQUAL 1.6)
|
||||
target_compile_definitions(alvin PRIVATE ALVIN_LIBEI_HAS_TEXT)
|
||||
endif()
|
||||
list(APPEND ALVIN_ENABLED_BACKENDS "libei-input")
|
||||
endif()
|
||||
|
||||
if(ALVIN_WITH_UINPUT)
|
||||
target_sources(alvin PRIVATE src/linux/uinput_input.cpp)
|
||||
target_compile_definitions(alvin PRIVATE ALVIN_HAVE_UINPUT)
|
||||
list(APPEND ALVIN_ENABLED_BACKENDS "uinput-input")
|
||||
endif()
|
||||
|
||||
list(APPEND ALVIN_ENABLED_BACKENDS "hyprland-windows")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Windows
|
||||
# ---------------------------------------------------------------------------
|
||||
elseif(WIN32)
|
||||
target_sources(alvin PRIVATE
|
||||
src/windows/platform_windows.cpp
|
||||
src/windows/sendinput_input.cpp
|
||||
src/windows/gdi_capture.cpp
|
||||
src/windows/win32_windows.cpp)
|
||||
target_compile_definitions(alvin PRIVATE UNICODE _UNICODE)
|
||||
target_link_libraries(alvin PRIVATE user32 gdi32 dwmapi)
|
||||
list(APPEND ALVIN_ENABLED_BACKENDS "sendinput-input" "gdi-capture" "win32-windows")
|
||||
else()
|
||||
message(FATAL_ERROR "Alvin does not support ${CMAKE_SYSTEM_NAME} yet")
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Optional OpenCV integration
|
||||
# ---------------------------------------------------------------------------
|
||||
find_package(OpenCV QUIET COMPONENTS core imgproc imgcodecs)
|
||||
alvin_resolve(ALVIN_WITH_VISION OpenCV_FOUND)
|
||||
if(ALVIN_WITH_VISION_ENABLED)
|
||||
add_library(alvin_vision src/vision/vision.cpp)
|
||||
add_library(alvin::vision ALIAS alvin_vision)
|
||||
target_link_libraries(alvin_vision PUBLIC alvin ${OpenCV_LIBS})
|
||||
set_target_properties(alvin_vision PROPERTIES EXPORT_NAME vision)
|
||||
if(WIN32)
|
||||
set_target_properties(alvin_vision PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||
endif()
|
||||
list(APPEND ALVIN_ENABLED_BACKENDS "vision(OpenCV ${OpenCV_VERSION})")
|
||||
endif()
|
||||
|
||||
message(STATUS "Alvin backends: ${ALVIN_ENABLED_BACKENDS}")
|
||||
|
||||
if(ALVIN_BUILD_EXAMPLES)
|
||||
add_subdirectory(examples)
|
||||
endif()
|
||||
|
||||
if(ALVIN_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Install + find_package(Alvin) support
|
||||
# ---------------------------------------------------------------------------
|
||||
include(CMakePackageConfigHelpers)
|
||||
set(ALVIN_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/Alvin)
|
||||
|
||||
set(_alvin_install_targets alvin)
|
||||
if(ALVIN_WITH_VISION_ENABLED)
|
||||
list(APPEND _alvin_install_targets alvin_vision)
|
||||
endif()
|
||||
install(TARGETS ${_alvin_install_targets} EXPORT AlvinTargets
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
|
||||
install(EXPORT AlvinTargets NAMESPACE alvin:: DESTINATION ${ALVIN_CMAKE_DIR})
|
||||
|
||||
# Values baked into AlvinConfig.cmake.
|
||||
get_target_property(_alvin_type alvin TYPE)
|
||||
if(_alvin_type STREQUAL "STATIC_LIBRARY" AND NOT ALVIN_CONFIG_DEPENDENCIES STREQUAL "")
|
||||
set(ALVIN_NEEDS_PKGCONFIG TRUE)
|
||||
else()
|
||||
set(ALVIN_NEEDS_PKGCONFIG FALSE)
|
||||
endif()
|
||||
if(ALVIN_WITH_VISION_ENABLED)
|
||||
set(ALVIN_HAS_VISION TRUE)
|
||||
else()
|
||||
set(ALVIN_HAS_VISION FALSE)
|
||||
endif()
|
||||
|
||||
configure_package_config_file(cmake/AlvinConfig.cmake.in
|
||||
${CMAKE_CURRENT_BINARY_DIR}/AlvinConfig.cmake
|
||||
INSTALL_DESTINATION ${ALVIN_CMAKE_DIR})
|
||||
write_basic_package_version_file(${CMAKE_CURRENT_BINARY_DIR}/AlvinConfigVersion.cmake
|
||||
COMPATIBILITY SameMinorVersion) # pre-1.0: minor versions may break the API
|
||||
install(FILES
|
||||
${CMAKE_CURRENT_BINARY_DIR}/AlvinConfig.cmake
|
||||
${CMAKE_CURRENT_BINARY_DIR}/AlvinConfigVersion.cmake
|
||||
DESTINATION ${ALVIN_CMAKE_DIR})
|
||||
Reference in new issue
Block a user