diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml index 95febfe8c..dfff1d9a6 100644 --- a/.github/workflows/push-master.yml +++ b/.github/workflows/push-master.yml @@ -215,12 +215,3 @@ jobs: prerelease: ${{ env.preRelease }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Dispatch event to build new HyperBian image - - name: Dispatch HyperBian build - uses: peter-evans/repository-dispatch@v1 - if: ${{ github.repository_owner == 'hyperion-project'}} - with: - repository: hyperion-project/HyperBian - token: ${{ secrets.HYPERION_BOT_TOKEN }} - event-type: hyperion_push diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..125c29a3a --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,17 @@ +name: Release Actions +on: + release: + types: [published] + +jobs: + hyperbian: + runs-on: ubuntu-latest + steps: + # Dispatch event to build new HyperBian image + - name: Dispatch HyperBian build + uses: peter-evans/repository-dispatch@v1 + if: ${{ github.repository_owner == 'hyperion-project'}} + with: + repository: hyperion-project/HyperBian + token: ${{ secrets.HYPERION_BOT_TOKEN }} + event-type: hyperion_push diff --git a/CHANGELOG.md b/CHANGELOG.md index ca41558d0..ecd3d1eda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Breaking ### Added - +- Add XCB grabber, a faster and safer alternative for X11 grabbing (#912) ### Changed ### Fixed diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index d9e960539..3eab1c2f4 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -18,6 +18,9 @@ // Define to enable the x11 grabber #cmakedefine ENABLE_X11 +// Define to enable the xcb grabber +#cmakedefine ENABLE_XCB + // Define to enable the qt grabber #cmakedefine ENABLE_QT diff --git a/assets/webconfig/js/content_dashboard.js b/assets/webconfig/js/content_dashboard.js index 48f46bdc9..ff5383a1e 100644 --- a/assets/webconfig/js/content_dashboard.js +++ b/assets/webconfig/js/content_dashboard.js @@ -104,7 +104,7 @@ $(document).ready( function() { if(grabbers.indexOf('dispmanx') > -1) html += 'Raspberry Pi'; - else if(grabbers.indexOf('x11') > -1) + else if(grabbers.indexOf('x11') > -1 || grabbers.indexOf('xcb') > -1) html += 'X86'; else if(grabbers.indexOf('osx') > -1) html += 'OSX'; diff --git a/assets/webconfig/js/content_grabber.js b/assets/webconfig/js/content_grabber.js index eae4c2f97..e5352d11b 100644 --- a/assets/webconfig/js/content_grabber.js +++ b/assets/webconfig/js/content_grabber.js @@ -323,7 +323,7 @@ $(document).ready( function() { if (grabbers.indexOf('dispmanx') > -1) hideEl(["device","pixelDecimation"]); - else if (grabbers.indexOf('x11') > -1) + else if (grabbers.indexOf('x11') > -1 || grabbers.indexOf('xcb') > -1) hideEl(["device","width","height"]); else if (grabbers.indexOf('osx') > -1 ) hideEl(["device","pixelDecimation"]); diff --git a/bin/install_hyperion.sh b/bin/install_hyperion.sh index 6e64a2592..5903e4168 100755 --- a/bin/install_hyperion.sh +++ b/bin/install_hyperion.sh @@ -236,6 +236,7 @@ else ln -fs $BINSP/hyperion-v4l2 $BINTP/hyperion-v4l2 ln -fs $BINSP/hyperion-dispmanx $BINTP/hyperion-dispmanx 2>/dev/null ln -fs $BINSP/hyperion-x11 $BINTP/hyperion-x11 2>/dev/null + ln -fs $BINSP/hyperion-xcb $BINTP/hyperion-xcb 2>/dev/null ln -fs $BINSP/hyperion-aml $BINTP/hyperion-aml 2>/dev/null fi @@ -260,6 +261,11 @@ elif [ $OS_OPENELEC -eq 1 ]; then echo '---> Adding Hyperion-x11 to OpenELEC/LibreELEC autostart.sh' echo "DISPLAY=:0.0 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/storage/hyperion/bin /storage/hyperion/bin/hyperion-x11 /storage/logfiles/hyperion.log 2>&1 &" >> /storage/.config/autostart.sh fi + # only add hyperion-xcb to startup, if not found and x32x64 detected + if [ $CPU_X32X64 -eq 1 ] && [ `cat /storage/.config/autostart.sh 2>/dev/null | grep hyperion-xcb | wc -l` -eq 0 ]; then + echo '---> Adding Hyperion-xcb to OpenELEC/LibreELEC autostart.sh' + echo "DISPLAY=:0.0 LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/storage/hyperion/bin /storage/hyperion/bin/hyperion-xcb /storage/logfiles/hyperion.log 2>&1 &" >> /storage/.config/autostart.sh + fi elif [ $USE_SYSTEMD -eq 1 ]; then echo '---> Installing systemd script' #place startup script for systemd and activate diff --git a/bin/remove_hyperion.sh b/bin/remove_hyperion.sh index e07489cf4..db094c72c 100644 --- a/bin/remove_hyperion.sh +++ b/bin/remove_hyperion.sh @@ -79,6 +79,7 @@ elif [ $OS_OPENELEC -eq 1 ]; then echo "---> Remove Hyperion from OpenELEC autostart.sh" sed -i "/hyperiond/d" /storage/.config/autostart.sh 2>/dev/null sed -i "/hyperion-x11/d" /storage/.config/autostart.sh 2>/dev/null + sed -i "/hyperion-xcb/d" /storage/.config/autostart.sh 2>/dev/null elif [ $USE_SYSTEMD -eq 1 ]; then # Delete and disable Hyperion systemd script echo '---> Delete and disable Hyperion systemd script' @@ -97,14 +98,15 @@ if [ $OS_OPENELEC -eq 1 ]; then echo '---> Remove the OpenELEC Hyperion binaries and hyperion.config.json' rm -rv /storage/hyperion 2>/dev/null rm -v /storage/.config/hyperion.config.json 2>/dev/null -else +else #Remove binaries on all distributions/systems (not OpenELEC) - echo "---> Remove links to the binaries" + echo "---> Remove links to the binaries" rm -v /usr/bin/hyperiond 2>/dev/null rm -v /usr/bin/hyperion-remote 2>/dev/null rm -v /usr/bin/hyperion-v4l2 2>/dev/null rm -v /usr/bin/hyperion-dispmanx 2>/dev/null rm -v /usr/bin/hyperion-x11 2>/dev/null + rm -v /usr/bin/hyperion-xcb 2>/dev/null rm -v /usr/bin/hyperion-aml 2>/dev/null rm -v /etc/hyperion.config.json 2>/dev/null echo "---> Remove binaries" @@ -116,4 +118,4 @@ echo '************************************************************************** echo 'Hyperion successful removed!' echo '*******************************************************************************' exit 0 - \ No newline at end of file + diff --git a/cmake/ECMFindModuleHelpers.cmake b/cmake/ECMFindModuleHelpers.cmake new file mode 100644 index 000000000..5e16b9498 --- /dev/null +++ b/cmake/ECMFindModuleHelpers.cmake @@ -0,0 +1,278 @@ +#.rst: +# ECMFindModuleHelpers +# -------------------- +# +# Helper macros for find modules: ecm_find_package_version_check(), +# ecm_find_package_parse_components() and +# ecm_find_package_handle_library_components(). +# +# :: +# +# ecm_find_package_version_check() +# +# Prints warnings if the CMake version or the project's required CMake version +# is older than that required by extra-cmake-modules. +# +# :: +# +# ecm_find_package_parse_components( +# RESULT_VAR +# KNOWN_COMPONENTS [ [...]] +# [SKIP_DEPENDENCY_HANDLING]) +# +# This macro will populate with a list of components found in +# _FIND_COMPONENTS, after checking that all those components are in the +# list of KNOWN_COMPONENTS; if there are any unknown components, it will print +# an error or warning (depending on the value of _FIND_REQUIRED) and call +# return(). +# +# The order of components in is guaranteed to match the order they +# are listed in the KNOWN_COMPONENTS argument. +# +# If SKIP_DEPENDENCY_HANDLING is not set, for each component the variable +# __component_deps will be checked for dependent components. +# If is listed in _FIND_COMPONENTS, then all its (transitive) +# dependencies will also be added to . +# +# :: +# +# ecm_find_package_handle_library_components( +# COMPONENTS [ [...]] +# [SKIP_DEPENDENCY_HANDLING]) +# [SKIP_PKG_CONFIG]) +# +# Creates an imported library target for each component. The operation of this +# macro depends on the presence of a number of CMake variables. +# +# The __lib variable should contain the name of this library, +# and __header variable should contain the name of a header +# file associated with it (whatever relative path is normally passed to +# '#include'). __header_subdir variable can be used to specify +# which subdirectory of the include path the headers will be found in. +# ecm_find_package_components() will then search for the library +# and include directory (creating appropriate cache variables) and create an +# imported library target named ::. +# +# Additional variables can be used to provide additional information: +# +# If SKIP_PKG_CONFIG, the __pkg_config variable is set, and +# pkg-config is found, the pkg-config module given by +# __pkg_config will be searched for and used to help locate the +# library and header file. It will also be used to set +# __VERSION. +# +# Note that if version information is found via pkg-config, +# __FIND_VERSION can be set to require a particular version +# for each component. +# +# If SKIP_DEPENDENCY_HANDLING is not set, the INTERFACE_LINK_LIBRARIES property +# of the imported target for will be set to contain the imported +# targets for the components listed in __component_deps. +# _FOUND will also be set to false if any of the compoments in +# __component_deps are not found. This requires the components +# in __component_deps to be listed before in the +# COMPONENTS argument. +# +# The following variables will be set: +# +# ``_TARGETS`` +# the imported targets +# ``_LIBRARIES`` +# the found libraries +# ``_INCLUDE_DIRS`` +# the combined required include directories for the components +# ``_DEFINITIONS`` +# the "other" CFLAGS provided by pkg-config, if any +# ``_VERSION`` +# the value of ``__VERSION`` for the first component that +# has this variable set (note that components are searched for in the order +# they are passed to the macro), although if it is already set, it will not +# be altered +# +# Note that these variables are never cleared, so if +# ecm_find_package_handle_library_components() is called multiple times with +# different components (typically because of multiple find_package() calls) then +# ``_TARGETS``, for example, will contain all the targets found in any +# call (although no duplicates). +# +# Since pre-1.0.0. + +#============================================================================= +# SPDX-FileCopyrightText: 2014 Alex Merry +# +# SPDX-License-Identifier: BSD-3-Clause + +include(CMakeParseArguments) + +macro(ecm_find_package_version_check module_name) + if(CMAKE_VERSION VERSION_LESS 2.8.12) + message(FATAL_ERROR "CMake 2.8.12 is required by Find${module_name}.cmake") + endif() + if(CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.12) + message(AUTHOR_WARNING "Your project should require at least CMake 2.8.12 to use Find${module_name}.cmake") + endif() +endmacro() + +macro(ecm_find_package_parse_components module_name) + set(ecm_fppc_options SKIP_DEPENDENCY_HANDLING) + set(ecm_fppc_oneValueArgs RESULT_VAR) + set(ecm_fppc_multiValueArgs KNOWN_COMPONENTS DEFAULT_COMPONENTS) + cmake_parse_arguments(ECM_FPPC "${ecm_fppc_options}" "${ecm_fppc_oneValueArgs}" "${ecm_fppc_multiValueArgs}" ${ARGN}) + + if(ECM_FPPC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments to ecm_find_package_parse_components: ${ECM_FPPC_UNPARSED_ARGUMENTS}") + endif() + if(NOT ECM_FPPC_RESULT_VAR) + message(FATAL_ERROR "Missing RESULT_VAR argument to ecm_find_package_parse_components") + endif() + if(NOT ECM_FPPC_KNOWN_COMPONENTS) + message(FATAL_ERROR "Missing KNOWN_COMPONENTS argument to ecm_find_package_parse_components") + endif() + if(NOT ECM_FPPC_DEFAULT_COMPONENTS) + set(ECM_FPPC_DEFAULT_COMPONENTS ${ECM_FPPC_KNOWN_COMPONENTS}) + endif() + + if(${module_name}_FIND_COMPONENTS) + set(ecm_fppc_requestedComps ${${module_name}_FIND_COMPONENTS}) + + if(NOT ECM_FPPC_SKIP_DEPENDENCY_HANDLING) + # Make sure deps are included + foreach(ecm_fppc_comp ${ecm_fppc_requestedComps}) + foreach(ecm_fppc_dep_comp ${${module_name}_${ecm_fppc_comp}_component_deps}) + list(FIND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}" ecm_fppc_index) + if("${ecm_fppc_index}" STREQUAL "-1") + if(NOT ${module_name}_FIND_QUIETLY) + message(STATUS "${module_name}: ${ecm_fppc_comp} requires ${${module_name}_${ecm_fppc_comp}_component_deps}") + endif() + list(APPEND ecm_fppc_requestedComps "${ecm_fppc_dep_comp}") + endif() + endforeach() + endforeach() + else() + message(STATUS "Skipping dependency handling for ${module_name}") + endif() + list(REMOVE_DUPLICATES ecm_fppc_requestedComps) + + # This makes sure components are listed in the same order as + # KNOWN_COMPONENTS (potentially important for inter-dependencies) + set(${ECM_FPPC_RESULT_VAR}) + foreach(ecm_fppc_comp ${ECM_FPPC_KNOWN_COMPONENTS}) + list(FIND ecm_fppc_requestedComps "${ecm_fppc_comp}" ecm_fppc_index) + if(NOT "${ecm_fppc_index}" STREQUAL "-1") + list(APPEND ${ECM_FPPC_RESULT_VAR} "${ecm_fppc_comp}") + list(REMOVE_AT ecm_fppc_requestedComps ${ecm_fppc_index}) + endif() + endforeach() + # if there are any left, they are unknown components + if(ecm_fppc_requestedComps) + set(ecm_fppc_msgType STATUS) + if(${module_name}_FIND_REQUIRED) + set(ecm_fppc_msgType FATAL_ERROR) + endif() + if(NOT ${module_name}_FIND_QUIETLY) + message(${ecm_fppc_msgType} "${module_name}: requested unknown components ${ecm_fppc_requestedComps}") + endif() + return() + endif() + else() + set(${ECM_FPPC_RESULT_VAR} ${ECM_FPPC_DEFAULT_COMPONENTS}) + endif() +endmacro() + +macro(ecm_find_package_handle_library_components module_name) + set(ecm_fpwc_options SKIP_PKG_CONFIG SKIP_DEPENDENCY_HANDLING) + set(ecm_fpwc_oneValueArgs) + set(ecm_fpwc_multiValueArgs COMPONENTS) + cmake_parse_arguments(ECM_FPWC "${ecm_fpwc_options}" "${ecm_fpwc_oneValueArgs}" "${ecm_fpwc_multiValueArgs}" ${ARGN}) + + if(ECM_FPWC_UNPARSED_ARGUMENTS) + message(FATAL_ERROR "Unexpected arguments to ecm_find_package_handle_components: ${ECM_FPWC_UNPARSED_ARGUMENTS}") + endif() + if(NOT ECM_FPWC_COMPONENTS) + message(FATAL_ERROR "Missing COMPONENTS argument to ecm_find_package_handle_components") + endif() + + include(FindPackageHandleStandardArgs) + find_package(PkgConfig) + foreach(ecm_fpwc_comp ${ECM_FPWC_COMPONENTS}) + set(ecm_fpwc_dep_vars) + set(ecm_fpwc_dep_targets) + if(NOT SKIP_DEPENDENCY_HANDLING) + foreach(ecm_fpwc_dep ${${module_name}_${ecm_fpwc_comp}_component_deps}) + list(APPEND ecm_fpwc_dep_vars "${module_name}_${ecm_fpwc_dep}_FOUND") + list(APPEND ecm_fpwc_dep_targets "${module_name}::${ecm_fpwc_dep}") + endforeach() + endif() + + if(NOT ECM_FPWC_SKIP_PKG_CONFIG AND ${module_name}_${ecm_fpwc_comp}_pkg_config) + pkg_check_modules(PKG_${module_name}_${ecm_fpwc_comp} QUIET + ${${module_name}_${ecm_fpwc_comp}_pkg_config}) + endif() + + find_path(${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + NAMES ${${module_name}_${ecm_fpwc_comp}_header} + HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_INCLUDE_DIRS} + PATH_SUFFIXES ${${module_name}_${ecm_fpwc_comp}_header_subdir} + ) + find_library(${module_name}_${ecm_fpwc_comp}_LIBRARY + NAMES ${${module_name}_${ecm_fpwc_comp}_lib} + HINTS ${PKG_${module_name}_${ecm_fpwc_comp}_LIBRARY_DIRS} + ) + + set(${module_name}_${ecm_fpwc_comp}_VERSION "${PKG_${module_name}_${ecm_fpwc_comp}_VERSION}") + if(NOT ${module_name}_VERSION) + set(${module_name}_VERSION ${${module_name}_${ecm_fpwc_comp}_VERSION}) + endif() + + set(FPHSA_NAME_MISMATCHED 1) + find_package_handle_standard_args(${module_name}_${ecm_fpwc_comp} + FOUND_VAR + ${module_name}_${ecm_fpwc_comp}_FOUND + REQUIRED_VARS + ${module_name}_${ecm_fpwc_comp}_LIBRARY + ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + ${ecm_fpwc_dep_vars} + VERSION_VAR + ${module_name}_${ecm_fpwc_comp}_VERSION + ) + unset(FPHSA_NAME_MISMATCHED) + + mark_as_advanced( + ${module_name}_${ecm_fpwc_comp}_LIBRARY + ${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR + ) + + if(${module_name}_${ecm_fpwc_comp}_FOUND) + list(APPEND ${module_name}_LIBRARIES + "${${module_name}_${ecm_fpwc_comp}_LIBRARY}") + list(APPEND ${module_name}_INCLUDE_DIRS + "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}") + set(${module_name}_DEFINITIONS + ${${module_name}_DEFINITIONS} + ${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}) + if(NOT TARGET ${module_name}::${ecm_fpwc_comp}) + add_library(${module_name}::${ecm_fpwc_comp} UNKNOWN IMPORTED) + set_target_properties(${module_name}::${ecm_fpwc_comp} PROPERTIES + IMPORTED_LOCATION "${${module_name}_${ecm_fpwc_comp}_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PKG_${module_name}_${ecm_fpwc_comp}_DEFINITIONS}" + INTERFACE_INCLUDE_DIRECTORIES "${${module_name}_${ecm_fpwc_comp}_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES "${ecm_fpwc_dep_targets}" + ) + endif() + list(APPEND ${module_name}_TARGETS + "${module_name}::${ecm_fpwc_comp}") + endif() + endforeach() + if(${module_name}_LIBRARIES) + list(REMOVE_DUPLICATES ${module_name}_LIBRARIES) + endif() + if(${module_name}_INCLUDE_DIRS) + list(REMOVE_DUPLICATES ${module_name}_INCLUDE_DIRS) + endif() + if(${module_name}_DEFINITIONS) + list(REMOVE_DUPLICATES ${module_name}_DEFINITIONS) + endif() + if(${module_name}_TARGETS) + list(REMOVE_DUPLICATES ${module_name}_TARGETS) + endif() +endmacro() diff --git a/cmake/FindXCB.cmake b/cmake/FindXCB.cmake new file mode 100644 index 000000000..315c3ca71 --- /dev/null +++ b/cmake/FindXCB.cmake @@ -0,0 +1,180 @@ +#.rst: +# FindXCB +# ------- +# +# Try to find XCB. +# +# This is a component-based find module, which makes use of the COMPONENTS and +# OPTIONAL_COMPONENTS arguments to find_module. The following components are +# available:: +# +# XCB +# ATOM AUX COMPOSITE CURSOR DAMAGE +# DPMS DRI2 DRI3 EVENT EWMH +# GLX ICCCM IMAGE KEYSYMS PRESENT +# RANDR RECORD RENDER RENDERUTIL RES +# SCREENSAVER SHAPE SHM SYNC UTIL +# XEVIE XF86DRI XFIXES XINERAMA XINPUT +# XKB XPRINT XTEST XV XVMC +# +# If no components are specified, this module will act as though all components +# except XINPUT (which is considered unstable) were passed to +# OPTIONAL_COMPONENTS. +# +# This module will define the following variables, independently of the +# components searched for or found: +# +# ``XCB_FOUND`` +# True if (the requestion version of) xcb is available +# ``XCB_VERSION`` +# Found xcb version +# ``XCB_TARGETS`` +# A list of all targets imported by this module (note that there may be more +# than the components that were requested) +# ``XCB_LIBRARIES`` +# This can be passed to target_link_libraries() instead of the imported +# targets +# ``XCB_INCLUDE_DIRS`` +# This should be passed to target_include_directories() if the targets are +# not used for linking +# ``XCB_DEFINITIONS`` +# This should be passed to target_compile_options() if the targets are not +# used for linking +# +# For each searched-for components, ``XCB__FOUND`` will be set to +# true if the corresponding xcb library was found, and false otherwise. If +# ``XCB__FOUND`` is true, the imported target ``XCB::`` +# will be defined. This module will also attempt to determine +# ``XCB_*_VERSION`` variables for each imported target, although +# ``XCB_VERSION`` should normally be sufficient. +# +# In general we recommend using the imported targets, as they are easier to use +# and provide more control. Bear in mind, however, that if any target is in the +# link interface of an exported library, it must be made available by the +# package config file. +# +# Since pre-1.0.0. + +#============================================================================= +# SPDX-FileCopyrightText: 2011 Fredrik Höglund +# SPDX-FileCopyrightText: 2013 Martin Gräßlin +# SPDX-FileCopyrightText: 2014-2015 Alex Merry +# +# SPDX-License-Identifier: BSD-3-Clause +#============================================================================= + +include(${CMAKE_SOURCE_DIR}/cmake/ECMFindModuleHelpers.cmake) + +ecm_find_package_version_check(XCB) + +# Note that this list needs to be ordered such that any component +# appears after its dependencies +set(XCB_known_components + XCB + RENDER + SHAPE + XFIXES + SHM + ATOM + AUX + COMPOSITE + CURSOR + DAMAGE + DPMS + DRI2 + DRI3 + EVENT + EWMH + GLX + ICCCM + IMAGE + KEYSYMS + PRESENT + RANDR + RECORD + RENDERUTIL + RES + SCREENSAVER + SYNC + UTIL + XEVIE + XF86DRI + XINERAMA + XINPUT + XKB + XPRINT + XTEST + XV + XVMC +) + +# XINPUT is unstable; do not include it by default +set(XCB_default_components ${XCB_known_components}) +list(REMOVE_ITEM XCB_default_components "XINPUT") + +# default component info: xcb components have fairly predictable +# header files, library names and pkg-config names +foreach(_comp ${XCB_known_components}) + string(TOLOWER "${_comp}" _lc_comp) + set(XCB_${_comp}_component_deps XCB) + set(XCB_${_comp}_pkg_config "xcb-${_lc_comp}") + set(XCB_${_comp}_lib "xcb-${_lc_comp}") + set(XCB_${_comp}_header "xcb/${_lc_comp}.h") +endforeach() +# exceptions +set(XCB_XCB_component_deps) +set(XCB_COMPOSITE_component_deps XCB XFIXES) +set(XCB_DAMAGE_component_deps XCB XFIXES) +set(XCB_IMAGE_component_deps XCB SHM) +set(XCB_RENDERUTIL_component_deps XCB RENDER) +set(XCB_XFIXES_component_deps XCB RENDER SHAPE) +set(XCB_XVMC_component_deps XCB XV) +set(XCB_XV_component_deps XCB SHM) +set(XCB_XCB_pkg_config "xcb") +set(XCB_XCB_lib "xcb") +set(XCB_ATOM_header "xcb/xcb_atom.h") +set(XCB_ATOM_lib "xcb-util") +set(XCB_AUX_header "xcb/xcb_aux.h") +set(XCB_AUX_lib "xcb-util") +set(XCB_CURSOR_header "xcb/xcb_cursor.h") +set(XCB_EVENT_header "xcb/xcb_event.h") +set(XCB_EVENT_lib "xcb-util") +set(XCB_EWMH_header "xcb/xcb_ewmh.h") +set(XCB_ICCCM_header "xcb/xcb_icccm.h") +set(XCB_IMAGE_header "xcb/xcb_image.h") +set(XCB_KEYSYMS_header "xcb/xcb_keysyms.h") +set(XCB_PIXEL_header "xcb/xcb_pixel.h") +set(XCB_RENDERUTIL_header "xcb/xcb_renderutil.h") +set(XCB_RENDERUTIL_lib "xcb-render-util") +set(XCB_UTIL_header "xcb/xcb_util.h") + +ecm_find_package_parse_components(XCB + RESULT_VAR XCB_components + KNOWN_COMPONENTS ${XCB_known_components} + DEFAULT_COMPONENTS ${XCB_default_components} +) + +list(FIND XCB_components "XINPUT" _XCB_XINPUT_index) +if (NOT _XCB_XINPUT_index EQUAL -1) + message(AUTHOR_WARNING "XINPUT from XCB was requested: this is EXPERIMENTAL and is likely to unavailable on many systems!") +endif() + +ecm_find_package_handle_library_components(XCB + COMPONENTS ${XCB_components} +) + +find_package_handle_standard_args(XCB + FOUND_VAR + XCB_FOUND + REQUIRED_VARS + XCB_LIBRARIES + VERSION_VAR + XCB_VERSION + HANDLE_COMPONENTS +) + +include(FeatureSummary) +set_package_properties(XCB PROPERTIES + URL "https://xcb.freedesktop.org/" + DESCRIPTION "X protocol C-language Binding" +) diff --git a/cmake/debian/postinst b/cmake/debian/postinst index 1b9131ae3..0527c94de 100644 --- a/cmake/debian/postinst +++ b/cmake/debian/postinst @@ -88,6 +88,7 @@ ln -fs $BINSP/hyperion-v4l2 $BINTP/hyperion-v4l2 ln -fs $BINSP/hyperion-framebuffer $BINTP/hyperion-framebuffer 2>/dev/null ln -fs $BINSP/hyperion-dispmanx $BINTP/hyperion-dispmanx 2>/dev/null ln -fs $BINSP/hyperion-x11 $BINTP/hyperion-x11 2>/dev/null +ln -fs $BINSP/hyperion-xcb $BINTP/hyperion-xcb 2>/dev/null ln -fs $BINSP/hyperion-aml $BINTP/hyperion-aml 2>/dev/null ln -fs $BINSP/hyperion-qt $BINTP/hyperion-qt 2>/dev/null diff --git a/cmake/packages.cmake b/cmake/packages.cmake index 36791decf..1376b4712 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -134,6 +134,9 @@ endif() if(ENABLE_X11) SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_x11" ) endif() +if(ENABLE_XCB) + SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_xcb" ) +endif() if(ENABLE_DISPMANX) SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_dispmanx" ) endif() @@ -210,6 +213,15 @@ if(ENABLE_X11) DEPENDS Hyperion ) endif() +if(ENABLE_X11) + cpack_add_component(hyperion_xcb + DISPLAY_NAME "XCB Standalone Screencap" + DESCRIPTION "XCB based standalone screen capture" + INSTALL_TYPES Full + GROUP Screencapture + DEPENDS Hyperion + ) +endif() if(ENABLE_DISPMANX) cpack_add_component(hyperion_dispmanx DISPLAY_NAME "RPi dispmanx Standalone Screencap" diff --git a/cmake/rpm/postinst b/cmake/rpm/postinst index 9c090cf10..836b7ff3d 100644 --- a/cmake/rpm/postinst +++ b/cmake/rpm/postinst @@ -88,6 +88,7 @@ ln -fs $BINSP/hyperion-v4l2 $BINTP/hyperion-v4l2 ln -fs $BINSP/hyperion-framebuffer $BINTP/hyperion-framebuffer 2>/dev/null ln -fs $BINSP/hyperion-dispmanx $BINTP/hyperion-dispmanx 2>/dev/null ln -fs $BINSP/hyperion-x11 $BINTP/hyperion-x11 2>/dev/null +ln -fs $BINSP/hyperion-xcb $BINTP/hyperion-xcb 2>/dev/null ln -fs $BINSP/hyperion-aml $BINTP/hyperion-aml 2>/dev/null ln -fs $BINSP/hyperion-qt $BINTP/hyperion-qt 2>/dev/null diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 43a38a7f9..afc3ae948 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -142,7 +142,7 @@ }, /// The configuration for the frame-grabber, contains the following items: - /// * type : type of grabber. (auto|osx|dispmanx|amlogic|x11|framebuffer|qt) [auto] + /// * type : type of grabber. (auto|osx|dispmanx|amlogic|x11|xcb|framebuffer|qt) [auto] /// * width : The width of the grabbed frames [pixels] /// * height : The height of the grabbed frames [pixels] /// * frequency_Hz : The frequency of the frame grab [Hz] @@ -161,7 +161,7 @@ "width" : 96, "height" : 96, - // valid for x11|qt + // valid for x11|xcb|qt "pixelDecimation" : 8, // valid for qt diff --git a/dependencies/external/flatbuffers b/dependencies/external/flatbuffers index c8fa0afdf..6942704f2 160000 --- a/dependencies/external/flatbuffers +++ b/dependencies/external/flatbuffers @@ -1 +1 @@ -Subproject commit c8fa0afdfc907452edbf2f5f3110f2f673fc4a70 +Subproject commit 6942704f2b4ce42aa64f371e4937f410437faf13 diff --git a/include/grabber/XcbGrabber.h b/include/grabber/XcbGrabber.h new file mode 100644 index 000000000..9a2ef1fbc --- /dev/null +++ b/include/grabber/XcbGrabber.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +class Logger; + +class XcbGrabber : public Grabber, public QAbstractNativeEventFilter +{ +public: + XcbGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation); + ~XcbGrabber() override; + + bool Setup(); + int grabFrame(Image & image, bool forceUpdate = false); + int updateScreenDimensions(bool force = false); + void setVideoMode(VideoMode mode) override; + bool setWidthHeight(int width, int height) override { return true; } + void setPixelDecimation(int pixelDecimation) override; + void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) override; + +private: + bool nativeEventFilter(const QByteArray & eventType, void * message, long int * result) override; + void freeResources(); + void setupResources(); + bool check_render() const; + bool check_randr() const; + bool check_shm() const; + xcb_screen_t * getScreen(const xcb_setup_t *setup, int screen_num) const; + xcb_render_pictformat_t findFormatForVisual(xcb_visualid_t visual) const; + + xcb_connection_t * _connection; + xcb_screen_t * _screen; + xcb_pixmap_t _pixmap; + xcb_render_pictformat_t _srcFormat; + xcb_render_pictformat_t _dstFormat; + xcb_render_picture_t _srcPicture; + xcb_render_picture_t _dstPicture; + xcb_render_transform_t _transform; + xcb_shm_seg_t _shminfo; + + int _pixelDecimation; + + unsigned _screenWidth; + unsigned _screenHeight; + unsigned _src_x; + unsigned _src_y; + + bool _XcbRenderAvailable; + bool _XcbRandRAvailable; + bool _XcbShmAvailable; + bool _XcbShmPixmapAvailable; + Logger * _logger; + + uint8_t * _shmData; + + int _XcbRandREventBase; +}; diff --git a/include/grabber/XcbWrapper.h b/include/grabber/XcbWrapper.h new file mode 100644 index 000000000..307c7c4b8 --- /dev/null +++ b/include/grabber/XcbWrapper.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +// some include of xorg defines "None" this is also used by QT and has to be undefined to avoid collisions +#ifdef None + #undef None +#endif + +class XcbWrapper: public GrabberWrapper +{ +public: + XcbWrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, const unsigned updateRate_Hz); + ~XcbWrapper() override; + +public slots: + virtual void action(); + +private: + XcbGrabber _grabber; + + bool _init; +}; diff --git a/include/hyperion/Grabber.h b/include/hyperion/Grabber.h index e07a448b6..0dc948f56 100644 --- a/include/hyperion/Grabber.h +++ b/include/hyperion/Grabber.h @@ -54,7 +54,7 @@ class Grabber : public QObject virtual bool setFramerate(int fps); /// - /// @brief Apply new pixelDecimation (used from x11 and qt) + /// @brief Apply new pixelDecimation (used from x11, xcb and qt) /// virtual void setPixelDecimation(int pixelDecimation) {} diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 6b0c30721..959bba93e 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -480,7 +480,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString QJsonObject grabbers; QJsonArray availableGrabbers; -#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) || defined(ENABLE_QT) +#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) || defined(ENABLE_XCB) || defined(ENABLE_QT) // get available grabbers //grabbers["active"] = ????; diff --git a/libsrc/grabber/CMakeLists.txt b/libsrc/grabber/CMakeLists.txt index 6163f5b3b..3faec2535 100644 --- a/libsrc/grabber/CMakeLists.txt +++ b/libsrc/grabber/CMakeLists.txt @@ -22,6 +22,10 @@ if (ENABLE_X11) add_subdirectory(x11) endif() +if (ENABLE_XCB) + add_subdirectory(xcb) +endif() + if (ENABLE_QT) add_subdirectory(qt) endif() diff --git a/libsrc/grabber/x11/X11Grabber.cpp b/libsrc/grabber/x11/X11Grabber.cpp index 130c9ed9c..645e30e98 100644 --- a/libsrc/grabber/x11/X11Grabber.cpp +++ b/libsrc/grabber/x11/X11Grabber.cpp @@ -72,8 +72,13 @@ void X11Grabber::setupResources() _shminfo.readOnly = False; XShmAttach(_x11Display, &_shminfo); } + if (_XRenderAvailable) { + _useImageResampler = false; + _imageResampler.setHorizontalPixelDecimation(1); + _imageResampler.setVerticalPixelDecimation(1); + if(_XShmPixmapAvailable) { _pixmap = XShmCreatePixmap(_x11Display, _window, _xImage->data, &_shminfo, _width, _height, _windowAttr.depth); @@ -86,8 +91,16 @@ void X11Grabber::setupResources() _dstFormat = XRenderFindVisualFormat(_x11Display, _windowAttr.visual); _srcPicture = XRenderCreatePicture(_x11Display, _window, _srcFormat, CPRepeat, &_pictAttr); _dstPicture = XRenderCreatePicture(_x11Display, _pixmap, _dstFormat, CPRepeat, &_pictAttr); + XRenderSetPictureFilter(_x11Display, _srcPicture, FilterBilinear, NULL, 0); } + else + { + _useImageResampler = true; + _imageResampler.setHorizontalPixelDecimation(_pixelDecimation); + _imageResampler.setVerticalPixelDecimation(_pixelDecimation); + } + } bool X11Grabber::Setup() @@ -117,10 +130,6 @@ bool X11Grabber::Setup() XShmQueryVersion(_x11Display, &dummy, &dummy, &pixmaps_supported); _XShmPixmapAvailable = pixmaps_supported && XShmPixmapFormat(_x11Display) == ZPixmap; - // Image scaling is performed by XRender when available, otherwise by ImageResampler - _imageResampler.setHorizontalPixelDecimation(_XRenderAvailable ? 1 : _pixelDecimation); - _imageResampler.setVerticalPixelDecimation(_XRenderAvailable ? 1 : _pixelDecimation); - bool result = (updateScreenDimensions(true) >=0); ErrorIf(!result, _log, "X11 Grabber start failed"); setEnabled(result); diff --git a/libsrc/grabber/x11/X11Wrapper.cpp b/libsrc/grabber/x11/X11Wrapper.cpp index eb89b3584..f44d4952b 100644 --- a/libsrc/grabber/x11/X11Wrapper.cpp +++ b/libsrc/grabber/x11/X11Wrapper.cpp @@ -23,13 +23,17 @@ void X11Wrapper::action() { stop(); } + else + { + if (_grabber.updateScreenDimensions() < 0 ) + { + stop(); + } + } } if (isActive()) { - if (_grabber.updateScreenDimensions() >= 0 ) - { - transferFrame(_grabber); - } + transferFrame(_grabber); } } diff --git a/libsrc/grabber/xcb/CMakeLists.txt b/libsrc/grabber/xcb/CMakeLists.txt new file mode 100644 index 000000000..ed81a112f --- /dev/null +++ b/libsrc/grabber/xcb/CMakeLists.txt @@ -0,0 +1,21 @@ +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/xcb) + +find_package(XCB COMPONENTS SHM IMAGE RENDER RANDR REQUIRED) +find_package(Qt5Widgets REQUIRED) +find_package(Qt5X11Extras REQUIRED) + +include_directories(${XCB_INCLUDE_DIRS}) + +FILE (GLOB XCB_SOURCES "${CURRENT_HEADER_DIR}/Xcb*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) + +add_library(xcb-grabber ${XCB_SOURCES}) + +target_link_libraries(xcb-grabber + hyperion + Qt5::X11Extras + Qt5::Widgets + ${XCB_LIBRARIES} +) + diff --git a/libsrc/grabber/xcb/XcbCommandExecutor.h b/libsrc/grabber/xcb/XcbCommandExecutor.h new file mode 100644 index 000000000..25e3a6076 --- /dev/null +++ b/libsrc/grabber/xcb/XcbCommandExecutor.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +#include + +template + std::unique_ptr + query(xcb_connection_t * connection, Args&& ...args) +{ + auto cookie = Request::RequestFunction(connection,args...); + + xcb_generic_error_t * error = nullptr; + std::unique_ptr xcbResponse( + Request::ReplyFunction(connection, cookie, &error), free); + + if (error) { + Logger * LOGGER = Logger::getInstance("XCB"); + Error(LOGGER, + "Cannot get the image data event_error: response_type:%u error_code:%u " + "sequence:%u resource_id:%u minor_code:%u major_code:%u.\n", + error->response_type, error->error_code, error->sequence, + error->resource_id, error->minor_code, error->major_code); + + free(error); + return {nullptr, nullptr}; + } + + return xcbResponse; +} diff --git a/libsrc/grabber/xcb/XcbCommands.h b/libsrc/grabber/xcb/XcbCommands.h new file mode 100644 index 000000000..1af713ec4 --- /dev/null +++ b/libsrc/grabber/xcb/XcbCommands.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + +struct GetImage +{ + typedef xcb_get_image_reply_t ResponseType; + + static constexpr auto RequestFunction = xcb_get_image; + static constexpr auto ReplyFunction = xcb_get_image_reply; +}; + +struct GetGeometry +{ + typedef xcb_get_geometry_reply_t ResponseType; + + static constexpr auto RequestFunction = xcb_get_geometry; + static constexpr auto ReplyFunction = xcb_get_geometry_reply; +}; + +struct ShmQueryVersion +{ + typedef xcb_shm_query_version_reply_t ResponseType; + + static constexpr auto RequestFunction = xcb_shm_query_version; + static constexpr auto ReplyFunction = xcb_shm_query_version_reply; +}; + +struct RenderQueryVersion +{ + typedef xcb_render_query_version_reply_t ResponseType; + + static constexpr auto RequestFunction = xcb_render_query_version; + static constexpr auto ReplyFunction = xcb_render_query_version_reply; +}; + +struct RandRQueryVersion +{ + typedef xcb_randr_query_version_reply_t ResponseType; + + static constexpr auto RequestFunction = xcb_randr_query_version; + static constexpr auto ReplyFunction = xcb_randr_query_version_reply; +}; + +struct ShmGetImage +{ + typedef xcb_shm_get_image_reply_t ResponseType; + + static constexpr auto RequestFunction = xcb_shm_get_image; + static constexpr auto ReplyFunction = xcb_shm_get_image_reply; +}; + +struct RenderQueryPictFormats +{ + typedef xcb_render_query_pict_formats_reply_t ResponseType; + + static constexpr auto RequestFunction = xcb_render_query_pict_formats; + static constexpr auto ReplyFunction = xcb_render_query_pict_formats_reply; +}; + diff --git a/libsrc/grabber/xcb/XcbGrabber.cpp b/libsrc/grabber/xcb/XcbGrabber.cpp new file mode 100644 index 000000000..207e1da2e --- /dev/null +++ b/libsrc/grabber/xcb/XcbGrabber.cpp @@ -0,0 +1,436 @@ +#include +#include + +#include "XcbCommands.h" +#include "XcbCommandExecutor.h" + +#include + +#include +#include + +#include + +#define DOUBLE_TO_FIXED(d) ((xcb_render_fixed_t) ((d) * 65536)) + +XcbGrabber::XcbGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation) + : Grabber("XCBGRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom) + , _connection{} + , _screen{} + , _pixmap{} + , _srcFormat{} + , _dstFormat{} + , _srcPicture{} + , _dstPicture{} + , _transform{} + , _shminfo{} + , _pixelDecimation(pixelDecimation) + , _screenWidth{} + , _screenHeight{} + , _src_x(cropLeft) + , _src_y(cropTop) + , _XcbRenderAvailable{} + , _XcbRandRAvailable{} + , _XcbShmAvailable{} + , _XcbShmPixmapAvailable{} + , _logger{} + , _shmData{} + , _XcbRandREventBase{} +{ + _logger = Logger::getInstance("XCB"); + + // cropping is performed by XcbRender, XcbShmGetImage or XcbGetImage + _useImageResampler = false; + _imageResampler.setCropping(0, 0, 0, 0); +} + +XcbGrabber::~XcbGrabber() +{ + if (_connection != nullptr) + { + freeResources(); + xcb_disconnect(_connection); + } +} + +void XcbGrabber::freeResources() +{ + if (_XcbRandRAvailable) + { + qApp->removeNativeEventFilter(this); + } + + if(_XcbShmAvailable) + { + xcb_shm_detach(_connection, _shminfo); + shmdt(_shmData); + shmctl(_shminfo, IPC_RMID, 0); + + } + + if (_XcbRenderAvailable) + { + xcb_free_pixmap(_connection, _pixmap); + xcb_render_free_picture(_connection, _srcPicture); + xcb_render_free_picture(_connection, _dstPicture); + } +} + +void XcbGrabber::setupResources() +{ + if (_XcbRandRAvailable) + { + qApp->installNativeEventFilter(this); + } + + if(_XcbShmAvailable) + { + _shminfo = xcb_generate_id(_connection); + int id = shmget(IPC_PRIVATE, _width * _height * 4, IPC_CREAT | 0777); + _shmData = static_cast(shmat(id, nullptr, 0)); + xcb_shm_attach(_connection, _shminfo, id, 0); + } + + if (_XcbRenderAvailable) + { + _useImageResampler = false; + _imageResampler.setHorizontalPixelDecimation(1); + _imageResampler.setVerticalPixelDecimation(1); + + if(_XcbShmPixmapAvailable) + { + _pixmap = xcb_generate_id(_connection); + xcb_shm_create_pixmap( + _connection, _pixmap, _screen->root, _width, + _height, _screen->root_depth, _shminfo, 0); + } + else + { + _pixmap = xcb_generate_id(_connection); + xcb_create_pixmap(_connection, _screen->root_depth, _pixmap, _screen->root, _width, _height); + } + + _srcFormat = findFormatForVisual(_screen->root_visual); + _dstFormat = findFormatForVisual(_screen->root_visual); + + _srcPicture = xcb_generate_id(_connection); + _dstPicture = xcb_generate_id(_connection); + + const uint32_t value_mask = XCB_RENDER_CP_REPEAT; + const uint32_t values[] = { XCB_RENDER_REPEAT_NONE }; + + xcb_render_create_picture(_connection, _srcPicture, _screen->root, _srcFormat, value_mask, values); + xcb_render_create_picture(_connection, _dstPicture, _pixmap, _dstFormat, value_mask, values); + + const std::string filter = "fast"; + xcb_render_set_picture_filter(_connection, _srcPicture, filter.size(), filter.c_str(), 0, nullptr); + } + else + { + _useImageResampler = true; + _imageResampler.setHorizontalPixelDecimation(_pixelDecimation); + _imageResampler.setVerticalPixelDecimation(_pixelDecimation); + } +} + +xcb_screen_t * XcbGrabber::getScreen(const xcb_setup_t *setup, int screen_num) const +{ + xcb_screen_iterator_t it = xcb_setup_roots_iterator(setup); + xcb_screen_t * screen = nullptr; + + for (; it.rem > 0; xcb_screen_next(&it)) + { + if (!screen_num) + { + screen = it.data; + break; + } + screen_num--; + } + return screen; +} + +bool XcbGrabber::check_randr() const +{ + return query(_connection, 0, 0) != nullptr; +} + +bool XcbGrabber::check_render() const +{ + return query(_connection, 0, 0) != nullptr; +} + +bool XcbGrabber::check_shm() const +{ + return query(_connection) != nullptr; +} + +bool XcbGrabber::Setup() +{ + int screen_num; + _connection = xcb_connect(nullptr, &screen_num); + + int ret = xcb_connection_has_error(_connection); + if (ret != 0) + { + Error(_logger, "Cannot open display, error %d", ret); + return false; + } + + const xcb_setup_t * setup = xcb_get_setup(_connection); + _screen = getScreen(setup, screen_num); + + if (!_screen) + { + Error(_log, "Unable to open display, screen %d does not exist", screen_num); + + if (getenv("DISPLAY")) + Error(_log, "%s", getenv("DISPLAY")); + else + Error(_log, "DISPLAY environment variable not set"); + + freeResources(); + return false; + } + + _XcbRandRAvailable = check_randr(); + _XcbRenderAvailable = check_render(); + _XcbShmAvailable = check_shm(); + + auto shmQueryReply = query(_connection); + _XcbShmPixmapAvailable = shmQueryReply->shared_pixmaps; + + bool result = (updateScreenDimensions(true) >=0); + ErrorIf(!result, _log, "XCB Grabber start failed"); + setEnabled(result); + return result; +} + +int XcbGrabber::grabFrame(Image & image, bool forceUpdate) +{ + if (!_enabled) + return 0; + + if (forceUpdate) + updateScreenDimensions(forceUpdate); + + if (_XcbRenderAvailable) + { + double scale_x = static_cast(_width / _pixelDecimation) / static_cast(_width); + double scale_y = static_cast(_height / _pixelDecimation) / static_cast(_height); + double scale = qMin(scale_y, scale_x); + + _transform = { + DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), + DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(1), DOUBLE_TO_FIXED(0), + DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(0), DOUBLE_TO_FIXED(scale) + }; + + xcb_render_set_picture_transform(_connection, _srcPicture, _transform); + xcb_render_composite(_connection, + XCB_RENDER_PICT_OP_SRC, _srcPicture, + XCB_RENDER_PICTURE_NONE, _dstPicture, + (_src_x/_pixelDecimation), + (_src_y/_pixelDecimation), + 0, 0, 0, 0, _width, _height); + + xcb_flush(_connection); + + if (_XcbShmAvailable) + { + query(_connection, + _pixmap, 0, 0, _width, _height, + ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, _shminfo, 0); + + _imageResampler.processImage( + reinterpret_cast(_shmData), + _width, _height, _width * 4, PixelFormat::BGR32, image); + } + else + { + auto result = query(_connection, + XCB_IMAGE_FORMAT_Z_PIXMAP, _pixmap, + 0, 0, _width, _height, ~0); + + auto buffer = xcb_get_image_data(result.get()); + + _imageResampler.processImage( + reinterpret_cast(buffer), + _width, _height, _width * 4, PixelFormat::BGR32, image); + } + + } + else if (_XcbShmAvailable) + { + query(_connection, + _screen->root, _src_x, _src_y, _width, _height, + ~0, XCB_IMAGE_FORMAT_Z_PIXMAP, _shminfo, 0); + + _imageResampler.processImage( + reinterpret_cast(_shmData), + _width, _height, _width * 4, PixelFormat::BGR32, image); + } + else + { + auto result = query(_connection, + XCB_IMAGE_FORMAT_Z_PIXMAP, _screen->root, + _src_x, _src_y, _width, _height, ~0); + + auto buffer = xcb_get_image_data(result.get()); + + _imageResampler.processImage( + reinterpret_cast(buffer), + _width, _height, _width * 4, PixelFormat::BGR32, image); + } + + return 0; +} + +int XcbGrabber::updateScreenDimensions(bool force) +{ + auto geometry = query(_connection, _screen->root); + if (geometry == nullptr) + { + setEnabled(false); + Error(_log, "Failed to obtain screen geometry"); + return -1; + } + + setEnabled(true); + + if (!force && _screenWidth == unsigned(geometry->width) && + _screenHeight == unsigned(geometry->height)) + return 0; + + if (_screenWidth || _screenHeight) + freeResources(); + + Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _screenWidth, _screenHeight, geometry->width, geometry->height); + + _screenWidth = geometry->width; + _screenHeight = geometry->height; + + int width = 0, height = 0; + + // Image scaling is performed by XRender when available, otherwise by ImageResampler + if (_XcbRenderAvailable) + { + width = (_screenWidth > unsigned(_cropLeft + _cropRight)) + ? ((_screenWidth - _cropLeft - _cropRight) / _pixelDecimation) + : _screenWidth / _pixelDecimation; + + height = (_screenHeight > unsigned(_cropTop + _cropBottom)) + ? ((_screenHeight - _cropTop - _cropBottom) / _pixelDecimation) + : _screenHeight / _pixelDecimation; + + Info(_log, "Using XcbRender for grabbing"); + } + else + { + width = (_screenWidth > unsigned(_cropLeft + _cropRight)) + ? (_screenWidth - _cropLeft - _cropRight) + : _screenWidth; + + height = (_screenHeight > unsigned(_cropTop + _cropBottom)) + ? (_screenHeight - _cropTop - _cropBottom) + : _screenHeight; + + Info(_log, "Using XcbGetImage for grabbing"); + } + + // Calculate final image dimensions and adjust top/left cropping in 3D modes + switch (_videoMode) + { + case VideoMode::VIDEO_3DSBS: + _width = width /2; + _height = height; + _src_x = _cropLeft / 2; + _src_y = _cropTop; + break; + case VideoMode::VIDEO_3DTAB: + _width = width; + _height = height / 2; + _src_x = _cropLeft; + _src_y = _cropTop / 2; + break; + case VideoMode::VIDEO_2D: + default: + _width = width; + _height = height; + _src_x = _cropLeft; + _src_y = _cropTop; + break; + } + + setupResources(); + + return 1; +} + +void XcbGrabber::setVideoMode(VideoMode mode) +{ + Grabber::setVideoMode(mode); + updateScreenDimensions(true); +} + +void XcbGrabber::setPixelDecimation(int pixelDecimation) +{ + if(_pixelDecimation != pixelDecimation) + { + _pixelDecimation = pixelDecimation; + updateScreenDimensions(true); + } +} + +void XcbGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) +{ + Grabber::setCropping(cropLeft, cropRight, cropTop, cropBottom); + if(_connection != nullptr) + updateScreenDimensions(true); +} + +bool XcbGrabber::nativeEventFilter(const QByteArray & eventType, void * message, long int * /*result*/) +{ + if (!_XcbRandRAvailable || eventType != "xcb_generic_event_t") + return false; + + xcb_generic_event_t *e = static_cast(message); + const uint8_t xEventType = XCB_EVENT_RESPONSE_TYPE(e); + + if (xEventType == _XcbRandREventBase + XCB_RANDR_SCREEN_CHANGE_NOTIFY) + updateScreenDimensions(true); + + return false; +} + +xcb_render_pictformat_t XcbGrabber::findFormatForVisual(xcb_visualid_t visual) const +{ + auto formats = query(_connection); + if (formats == nullptr) + return {}; + + int screen = QX11Info::appScreen(); + xcb_render_pictscreen_iterator_t sit = + xcb_render_query_pict_formats_screens_iterator(formats.get()); + + for (; sit.rem; --screen, xcb_render_pictscreen_next(&sit)) { + if (screen != 0) + continue; + + xcb_render_pictdepth_iterator_t dit = + xcb_render_pictscreen_depths_iterator(sit.data); + for (; dit.rem; xcb_render_pictdepth_next(&dit)) + { + xcb_render_pictvisual_iterator_t vit + = xcb_render_pictdepth_visuals_iterator(dit.data); + for (; vit.rem; xcb_render_pictvisual_next(&vit)) + { + if (vit.data->visual == visual) + { + return vit.data->format; + } + } + } + } + return {}; +} diff --git a/libsrc/grabber/xcb/XcbWrapper.cpp b/libsrc/grabber/xcb/XcbWrapper.cpp new file mode 100644 index 000000000..699b5bd43 --- /dev/null +++ b/libsrc/grabber/xcb/XcbWrapper.cpp @@ -0,0 +1,39 @@ +#include + +XcbWrapper::XcbWrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, const unsigned updateRate_Hz) + : GrabberWrapper("Xcb", &_grabber, 0, 0, updateRate_Hz) + , _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation) + , _init(false) +{} + +XcbWrapper::~XcbWrapper() +{ + if ( _init ) + { + stop(); + } +} + +void XcbWrapper::action() +{ + if (! _init ) + { + _init = true; + if ( ! _grabber.Setup() ) + { + stop(); + } + else + { + if (_grabber.updateScreenDimensions() < 0 ) + { + stop(); + } + } + } + + if (isActive()) + { + transferFrame(_grabber); + } +} diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index aa1bd106d..42f02364b 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -94,6 +94,10 @@ QStringList GrabberWrapper::availableGrabbers() grabbers << "x11"; #endif + #ifdef ENABLE_XCB + grabbers << "xcb"; + #endif + #ifdef ENABLE_QT grabbers << "qt"; #endif diff --git a/libsrc/hyperion/schema/schema-framegrabber.json b/libsrc/hyperion/schema/schema-framegrabber.json index a5836485a..4ef1cfb73 100644 --- a/libsrc/hyperion/schema/schema-framegrabber.json +++ b/libsrc/hyperion/schema/schema-framegrabber.json @@ -7,10 +7,10 @@ { "type" : "string", "title" : "edt_conf_fg_type_title", - "enum" : ["auto","dispmanx","amlogic","x11","framebuffer","qt"], + "enum" : ["auto","dispmanx","amlogic","x11", "xcb", "framebuffer","qt"], "options": { - "enum_titles": ["edt_conf_enum_automatic","DispmanX","AMLogic","X11","Framebuffer","QT"] + "enum_titles": ["edt_conf_enum_automatic","DispmanX","AMLogic","X11", "XCB", "Framebuffer","QT"] }, "default" : "auto", "propertyOrder" : 2 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f6954f3ce..87200b431 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -14,6 +14,10 @@ if(ENABLE_X11) add_subdirectory(hyperion-x11) endif() +if(ENABLE_XCB) + add_subdirectory(hyperion-xcb) +endif() + if(ENABLE_DISPMANX) add_subdirectory(hyperion-dispmanx) endif() diff --git a/src/hyperion-xcb/CMakeLists.txt b/src/hyperion-xcb/CMakeLists.txt new file mode 100644 index 000000000..8c082d848 --- /dev/null +++ b/src/hyperion-xcb/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.0.0) +project(hyperion-xcb) + +include_directories( + ${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/flatbufserver + ${FLATBUFFERS_INCLUDE_DIRS} +) + +set(Hyperion_XCB_HEADERS + XcbWrapper.h +) + +set(Hyperion_XCB_SOURCES + hyperion-xcb.cpp + XcbWrapper.cpp +) + +add_executable(${PROJECT_NAME} + ${Hyperion_XCB_HEADERS} + ${Hyperion_XCB_SOURCES} +) + +target_link_libraries(${PROJECT_NAME} + commandline + hyperion-utils + flatbufserver + flatbuffers + xcb-grabber + ssdp +) + +install (TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_xcb") + +if(CMAKE_HOST_UNIX) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_xcb" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_xcb" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_xcb" ) +endif(CMAKE_HOST_UNIX) diff --git a/src/hyperion-xcb/XcbWrapper.cpp b/src/hyperion-xcb/XcbWrapper.cpp new file mode 100644 index 000000000..c7436571e --- /dev/null +++ b/src/hyperion-xcb/XcbWrapper.cpp @@ -0,0 +1,47 @@ + +// Hyperion-Xcb includes +#include "XcbWrapper.h" + +XcbWrapper::XcbWrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation) : + _timer(this), + _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation) +{ + _timer.setSingleShot(false); + _timer.setInterval(grabInterval); + + // Connect capturing to the timeout signal of the timer + connect(&_timer, SIGNAL(timeout()), this, SLOT(capture())); +} + +const Image & XcbWrapper::getScreenshot() +{ + _grabber.grabFrame(_screenshot, true); + return _screenshot; +} + +void XcbWrapper::start() +{ + _timer.start(); +} + +void XcbWrapper::stop() +{ + _timer.stop(); +} + +bool XcbWrapper::displayInit() +{ + return _grabber.Setup(); +} + +void XcbWrapper::capture() +{ + _grabber.grabFrame(_screenshot, !_inited); + emit sig_screenshot(_screenshot); + _inited = true; +} + +void XcbWrapper::setVideoMode(const VideoMode mode) +{ + _grabber.setVideoMode(mode); +} diff --git a/src/hyperion-xcb/XcbWrapper.h b/src/hyperion-xcb/XcbWrapper.h new file mode 100644 index 000000000..7b099fc9d --- /dev/null +++ b/src/hyperion-xcb/XcbWrapper.h @@ -0,0 +1,57 @@ +#pragma once + +// QT includes +#include + +// Hyperion-Xcb includes +#include + +//Utils includes +#include + +class XcbWrapper : public QObject +{ + Q_OBJECT +public: + XcbWrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation); + + const Image & getScreenshot(); + + /// + /// Starts the timed capturing of screenshots + /// + void start(); + + void stop(); + + bool displayInit(); + +signals: + void sig_screenshot(const Image & screenshot); + +public slots: + /// + /// Set the video mode (2D/3D) + /// @param[in] mode The new video mode + /// + void setVideoMode(const VideoMode videoMode); + +private slots: + /// + /// Performs a single screenshot capture and publishes the capture screenshot on the screenshot + /// signal. + /// + void capture(); + +private: + /// The QT timer to generate capture-publish events + QTimer _timer; + + /// The grabber for creating screenshots + XcbGrabber _grabber; + + Image _screenshot; + + // prevent cont dimension updates + bool _inited = false; +}; diff --git a/src/hyperion-xcb/hyperion-xcb.cpp b/src/hyperion-xcb/hyperion-xcb.cpp new file mode 100644 index 000000000..607c51a5b --- /dev/null +++ b/src/hyperion-xcb/hyperion-xcb.cpp @@ -0,0 +1,115 @@ + +// QT includes +#include +#include + +#include +#include +#include +#include "XcbWrapper.h" +#include "HyperionConfig.h" + +// ssdp discover +#include + +using namespace commandline; + +// save the image as screenshot +void saveScreenshot(QString filename, const Image & image) +{ + // store as PNG + QImage pngImage((const uint8_t *) image.memptr(), image.width(), image.height(), 3*image.width(), QImage::Format_RGB888); + pngImage.save(filename); +} + +int main(int argc, char ** argv) +{ + std::cout + << "hyperion-xcb:" << std::endl + << "\tVersion : " << HYPERION_VERSION << " (" << HYPERION_BUILD_ID << ")" << std::endl + << "\tbuild time: " << __DATE__ << " " << __TIME__ << std::endl; + + DefaultSignalHandler::install(); + + QApplication app(argc, argv); + try + { + // create the option parser and initialize all parameters + Parser parser("XCB capture application for Hyperion. Will automatically search a Hyperion server if -a option isn't used. Please note that if you have more than one server running it's more or less random which one will be used."); + + IntOption & argFps = parser.add ('f', "framerate", "Capture frame rate [default: %1]", "10"); + IntOption & argCropWidth = parser.add (0x0, "crop-width", "Number of pixels to crop from the left and right sides of the picture before decimation [default: %1]", "0"); + IntOption & argCropHeight = parser.add (0x0, "crop-height", "Number of pixels to crop from the top and the bottom of the picture before decimation [default: %1]", "0"); + IntOption & argCropLeft = parser.add (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)"); + IntOption & argCropRight = parser.add (0x0, "crop-right", "Number of pixels to crop from the right of the picture before decimation (overrides --crop-width)"); + IntOption & argCropTop = parser.add (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)"); + IntOption & argCropBottom = parser.add (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)"); + IntOption & argSizeDecimation = parser.add ('s', "size-decimator", "Decimation factor for the output size [default=%1]", "8", 1); + BooleanOption & argScreenshot = parser.add(0x0, "screenshot", "Take a single screenshot, save it to file and quit"); + Option & argAddress = parser.add