Feature/xcb grabber (#912)

* Add Xcb grabber

* update compile instruction

Signed-off-by: Paulchen Panther <Paulchen-Panter@protonmail.com>

* Fix problem on resolution change + Make XCB default if X11 is not avaialable

* Fix decimation problem

Co-authored-by: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com>
Co-authored-by: Paulchen Panther <Paulchen-Panter@protonmail.com>
This commit is contained in:
Murat Seker 2020-08-03 12:31:39 +02:00 committed by GitHub
parent 11d7614591
commit 13205a9d11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 1545 additions and 20 deletions

View File

@ -9,6 +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

View File

@ -40,6 +40,7 @@ SET ( DEFAULT_AMLOGIC OFF )
SET ( DEFAULT_DISPMANX OFF )
SET ( DEFAULT_OSX OFF )
SET ( DEFAULT_X11 OFF )
SET ( DEFAULT_XCB OFF )
SET ( DEFAULT_QT ON )
SET ( DEFAULT_WS281XPWM OFF )
SET ( DEFAULT_AVAHI ON )
@ -122,6 +123,7 @@ elseif ( "${PLATFORM}" STREQUAL "amlogic64" )
SET ( DEFAULT_AMLOGIC ON )
elseif ( "${PLATFORM}" MATCHES "x11" )
SET ( DEFAULT_X11 ON )
SET ( DEFAULT_XCB ON )
if ( "${PLATFORM}" STREQUAL "x11-dev" )
SET ( DEFAULT_AMLOGIC ON)
SET ( DEFAULT_WS281XPWM ON )
@ -182,6 +184,9 @@ message(STATUS "ENABLE_CEC = ${ENABLE_CEC}")
option(ENABLE_X11 "Enable the X11 grabber" ${DEFAULT_X11})
message(STATUS "ENABLE_X11 = ${ENABLE_X11}")
option(ENABLE_XCB "Enable the XCB grabber" ${DEFAULT_XCB})
message(STATUS "ENABLE_XCB = ${ENABLE_XCB}")
option(ENABLE_QT "Enable the qt grabber" ${DEFAULT_QT})
message(STATUS "ENABLE_QT = ${ENABLE_QT}")

View File

@ -40,7 +40,7 @@ wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/
```
sudo apt-get update
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite libssl-dev zlib1g-dev
sudo apt-get install git cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libqt5x11extras5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libssl-dev zlib1g-dev
```
**on RPI you need the videocore IV headers**

View File

@ -11,7 +11,7 @@ Update the Ubuntu environment to the latest stage and install required additiona
```
sudo apt-get update
sudo apt-get upgrade
sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libcec-dev libxcb-util0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libqt5sql5-sqlite libssl-dev zlib1g-dev
sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libqt5sql5-sqlite libusb-1.0-0-dev python3-dev libcec-dev libxcb-image0-dev libxcb-util0-dev libxcb-shm0-dev libxcb-render0-dev libxcb-randr0-dev libxrandr-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev libjpeg-dev libturbojpeg0-dev libssl-dev zlib1g-dev
```
Refine the target IP or hostname, plus userID as required and set-up cross-compilation environment:

View File

@ -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

View File

@ -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';

View File

@ -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"]);

View File

@ -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 </dev/null >/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 </dev/null >/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

View File

@ -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'
@ -105,6 +106,7 @@ else
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"

View File

@ -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(<name>)
#
# 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(<name>
# RESULT_VAR <variable>
# KNOWN_COMPONENTS <component1> [<component2> [...]]
# [SKIP_DEPENDENCY_HANDLING])
#
# This macro will populate <variable> with a list of components found in
# <name>_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 <name>_FIND_REQUIRED) and call
# return().
#
# The order of components in <variable> 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
# <name>_<component>_component_deps will be checked for dependent components.
# If <component> is listed in <name>_FIND_COMPONENTS, then all its (transitive)
# dependencies will also be added to <variable>.
#
# ::
#
# ecm_find_package_handle_library_components(<name>
# COMPONENTS <component> [<component> [...]]
# [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 <name>_<component>_lib variable should contain the name of this library,
# and <name>_<component>_header variable should contain the name of a header
# file associated with it (whatever relative path is normally passed to
# '#include'). <name>_<component>_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 <name>::<component>.
#
# Additional variables can be used to provide additional information:
#
# If SKIP_PKG_CONFIG, the <name>_<component>_pkg_config variable is set, and
# pkg-config is found, the pkg-config module given by
# <name>_<component>_pkg_config will be searched for and used to help locate the
# library and header file. It will also be used to set
# <name>_<component>_VERSION.
#
# Note that if version information is found via pkg-config,
# <name>_<component>_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 <component> will be set to contain the imported
# targets for the components listed in <name>_<component>_component_deps.
# <component>_FOUND will also be set to false if any of the compoments in
# <name>_<component>_component_deps are not found. This requires the components
# in <name>_<component>_component_deps to be listed before <component> in the
# COMPONENTS argument.
#
# The following variables will be set:
#
# ``<name>_TARGETS``
# the imported targets
# ``<name>_LIBRARIES``
# the found libraries
# ``<name>_INCLUDE_DIRS``
# the combined required include directories for the components
# ``<name>_DEFINITIONS``
# the "other" CFLAGS provided by pkg-config, if any
# ``<name>_VERSION``
# the value of ``<name>_<component>_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
# ``<name>_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 <alex.merry@kde.org>
#
# 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()

180
cmake/FindXCB.cmake Normal file
View File

@ -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_<component>_FOUND`` will be set to
# true if the corresponding xcb library was found, and false otherwise. If
# ``XCB_<component>_FOUND`` is true, the imported target ``XCB::<component>``
# 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 <fredrik@kde.org>
# SPDX-FileCopyrightText: 2013 Martin Gräßlin <mgraesslin@kde.org>
# SPDX-FileCopyrightText: 2014-2015 Alex Merry <alex.merry@kde.org>
#
# 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"
)

View File

@ -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

View File

@ -135,6 +135,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()
@ -211,6 +214,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"

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,71 @@
#pragma once
#include <QAbstractNativeEventFilter>
#include <QObject>
#include <utils/ColorRgb.h>
#include <hyperion/Grabber.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <xcb/randr.h>
#include <xcb/shm.h>
#include <xcb/xcb.h>
#include <xcb/xcb_image.h>
class Logger;
class XcbGrabber : public Grabber, public QAbstractNativeEventFilter
{
Q_OBJECT
public:
XcbGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation);
~XcbGrabber() override;
bool Setup();
int grabFrame(Image<ColorRgb> & 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();
void setupRender();
void setupRandr();
void setupShm();
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;
};

View File

@ -0,0 +1,24 @@
#pragma once
#include <hyperion/GrabberWrapper.h>
#include <grabber/XcbGrabber.h>
// 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;
};

View File

@ -54,7 +54,7 @@ public:
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) {}

View File

@ -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"] = ????;

View File

@ -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()

View File

@ -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);

View File

@ -23,13 +23,17 @@ void X11Wrapper::action()
{
stop();
}
else
{
if (_grabber.updateScreenDimensions() < 0 )
{
stop();
}
}
}
if (isActive())
{
if (_grabber.updateScreenDimensions() >= 0 )
{
transferFrame(_grabber);
}
transferFrame(_grabber);
}
}

View File

@ -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}
)

View File

@ -0,0 +1,30 @@
#pragma once
#include <memory>
#include <xcb/xcb.h>
template<class Request, class ...Args>
std::unique_ptr<typename Request::ResponseType, decltype(&free)>
query(xcb_connection_t * connection, Args&& ...args)
{
auto cookie = Request::RequestFunction(connection,args...);
xcb_generic_error_t * error = nullptr;
std::unique_ptr<typename Request::ResponseType, decltype(&free)> 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;
}

View File

@ -0,0 +1,55 @@
#pragma once
#include <xcb/randr.h>
#include <xcb/shm.h>
#include <xcb/xcb.h>
#include <xcb/xcb_image.h>
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 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;
};

View File

@ -0,0 +1,452 @@
#include <utils/Logger.h>
#include <grabber/XcbGrabber.h>
#include "XcbCommands.h"
#include "XcbCommandExecutor.h"
#include <xcb/xcb_event.h>
#include <QCoreApplication>
#include <QX11Info>
#include <memory>
#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{-1}
{
_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<uint8_t*>(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;
}
void XcbGrabber::setupRandr()
{
auto randrQueryExtensionReply = xcb_get_extension_data(_connection, &xcb_randr_id);
_XcbRandRAvailable = randrQueryExtensionReply != nullptr;
_XcbRandREventBase = randrQueryExtensionReply ? randrQueryExtensionReply->first_event : -1;
}
void XcbGrabber::setupRender()
{
auto renderQueryVersionReply = query<RenderQueryVersion>(_connection, 0, 0);
_XcbRenderAvailable = renderQueryVersionReply != nullptr;
}
void XcbGrabber::setupShm()
{
auto shmQueryExtensionReply = xcb_get_extension_data(_connection, &xcb_render_id);
_XcbShmAvailable = shmQueryExtensionReply != nullptr;
_XcbShmPixmapAvailable = false;
if (_XcbShmAvailable)
{
auto shmQueryVersionReply = query<ShmQueryVersion>(_connection);
_XcbShmPixmapAvailable = shmQueryVersionReply ? shmQueryVersionReply->shared_pixmaps : false;
}
}
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;
}
setupRandr();
setupRender();
setupShm();
Info(_log, "XcbRandR : %s", _XcbRandRAvailable ? "available" : "unavailable");
Info(_log, "XcbRender : %s", _XcbRenderAvailable ? "available" : "unavailable");
Info(_log, "XcbShm : %s", _XcbShmAvailable ? "available" : "unavailable");
Info(_log, "XcbPixmap : %s", _XcbShmPixmapAvailable ? "available" : "unavailable");
bool result = (updateScreenDimensions(true) >=0);
ErrorIf(!result, _log, "XCB Grabber start failed");
setEnabled(result);
return result;
}
int XcbGrabber::grabFrame(Image<ColorRgb> & image, bool forceUpdate)
{
if (!_enabled)
return 0;
if (forceUpdate)
updateScreenDimensions(forceUpdate);
if (_XcbRenderAvailable)
{
double scale_x = static_cast<double>(_screenWidth / _pixelDecimation) / static_cast<double>(_screenWidth);
double scale_y = static_cast<double>(_screenHeight / _pixelDecimation) / static_cast<double>(_screenHeight);
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<ShmGetImage>(_connection,
_pixmap, 0, 0, _width, _height,
~0, XCB_IMAGE_FORMAT_Z_PIXMAP, _shminfo, 0);
_imageResampler.processImage(
reinterpret_cast<const uint8_t *>(_shmData),
_width, _height, _width * 4, PixelFormat::BGR32, image);
}
else
{
auto result = query<GetImage>(_connection,
XCB_IMAGE_FORMAT_Z_PIXMAP, _pixmap,
0, 0, _width, _height, ~0);
auto buffer = xcb_get_image_data(result.get());
_imageResampler.processImage(
reinterpret_cast<const uint8_t *>(buffer),
_width, _height, _width * 4, PixelFormat::BGR32, image);
}
}
else if (_XcbShmAvailable)
{
query<ShmGetImage>(_connection,
_screen->root, _src_x, _src_y, _width, _height,
~0, XCB_IMAGE_FORMAT_Z_PIXMAP, _shminfo, 0);
_imageResampler.processImage(
reinterpret_cast<const uint8_t *>(_shmData),
_width, _height, _width * 4, PixelFormat::BGR32, image);
}
else
{
auto result = query<GetImage>(_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<const uint8_t *>(buffer),
_width, _height, _width * 4, PixelFormat::BGR32, image);
}
return 0;
}
int XcbGrabber::updateScreenDimensions(bool force)
{
auto geometry = query<GetGeometry>(_connection, _screen->root);
if (geometry == nullptr)
{
setEnabled(false);
Error(_log, "Failed to obtain screen geometry");
return -1;
}
if (!_enabled)
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 [%dx%d]", width, height);
}
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 [%dx%d]", width, height);
}
// 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" || _XcbRandREventBase == -1)
return false;
xcb_generic_event_t *e = static_cast<xcb_generic_event_t*>(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<RenderQueryPictFormats>(_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 {};
}

View File

@ -0,0 +1,39 @@
#include <grabber/XcbWrapper.h>
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);
}
}

View File

@ -94,6 +94,10 @@ QStringList GrabberWrapper::availableGrabbers()
grabbers << "x11";
#endif
#ifdef ENABLE_XCB
grabbers << "xcb";
#endif
#ifdef ENABLE_QT
grabbers << "qt";
#endif

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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<ColorRgb> & 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);
}

View File

@ -0,0 +1,57 @@
#pragma once
// QT includes
#include <QTimer>
// Hyperion-Xcb includes
#include <grabber/XcbGrabber.h>
//Utils includes
#include <utils/VideoMode.h>
class XcbWrapper : public QObject
{
Q_OBJECT
public:
XcbWrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation);
const Image<ColorRgb> & getScreenshot();
///
/// Starts the timed capturing of screenshots
///
void start();
void stop();
bool displayInit();
signals:
void sig_screenshot(const Image<ColorRgb> & 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<ColorRgb> _screenshot;
// prevent cont dimension updates
bool _inited = false;
};

View File

@ -0,0 +1,115 @@
// QT includes
#include <QApplication>
#include <QImage>
#include <commandline/Parser.h>
#include <flatbufserver/FlatBufferConnection.h>
#include <utils/DefaultSignalHandler.h>
#include "XcbWrapper.h"
#include "HyperionConfig.h"
// ssdp discover
#include <ssdp/SSDPDiscover.h>
using namespace commandline;
// save the image as screenshot
void saveScreenshot(QString filename, const Image<ColorRgb> & 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<IntOption> ('f', "framerate", "Capture frame rate [default: %1]", "10");
IntOption & argCropWidth = parser.add<IntOption> (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<IntOption> (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<IntOption> (0x0, "crop-left", "Number of pixels to crop from the left of the picture before decimation (overrides --crop-width)");
IntOption & argCropRight = parser.add<IntOption> (0x0, "crop-right", "Number of pixels to crop from the right of the picture before decimation (overrides --crop-width)");
IntOption & argCropTop = parser.add<IntOption> (0x0, "crop-top", "Number of pixels to crop from the top of the picture before decimation (overrides --crop-height)");
IntOption & argCropBottom = parser.add<IntOption> (0x0, "crop-bottom", "Number of pixels to crop from the bottom of the picture before decimation (overrides --crop-height)");
IntOption & argSizeDecimation = parser.add<IntOption> ('s', "size-decimator", "Decimation factor for the output size [default=%1]", "8", 1);
BooleanOption & argScreenshot = parser.add<BooleanOption>(0x0, "screenshot", "Take a single screenshot, save it to file and quit");
Option & argAddress = parser.add<Option> ('a', "address", "Set the address of the hyperion server [default: %1]", "127.0.0.1:19400");
IntOption & argPriority = parser.add<IntOption> ('p', "priority", "Use the provided priority channel (suggested 100-199) [default: %1]", "150");
BooleanOption & argSkipReply = parser.add<BooleanOption>(0x0, "skip-reply", "Do not receive and check reply messages from Hyperion");
BooleanOption & argHelp = parser.add<BooleanOption>('h', "help", "Show this help message and exit");
// parse all options
parser.process(app);
// check if we need to display the usage. exit if we do.
if (parser.isSet(argHelp))
{
parser.showHelp(0);
}
// Create the XCB grabbing stuff
XcbWrapper xcbWrapper(
1000 / argFps.getInt(parser),
parser.isSet(argCropLeft) ? argCropLeft.getInt(parser) : argCropWidth.getInt(parser),
parser.isSet(argCropRight) ? argCropRight.getInt(parser) : argCropWidth.getInt(parser),
parser.isSet(argCropTop) ? argCropTop.getInt(parser) : argCropHeight.getInt(parser),
parser.isSet(argCropBottom) ? argCropBottom.getInt(parser) : argCropHeight.getInt(parser),
argSizeDecimation.getInt(parser)); // decimation
if (!xcbWrapper.displayInit())
return -1;
if (parser.isSet(argScreenshot))
{
// Capture a single screenshot and finish
const Image<ColorRgb> & screenshot = xcbWrapper.getScreenshot();
saveScreenshot("screenshot.png", screenshot);
}
else
{
// server searching by ssdp
QString address = argAddress.value(parser);
if(argAddress.value(parser) == "127.0.0.1:19400")
{
SSDPDiscover discover;
address = discover.getFirstService(searchType::STY_FLATBUFSERVER);
if(address.isEmpty())
{
address = argAddress.value(parser);
}
}
// Create the Flatbuf-connection
FlatBufferConnection flatbuf("XCB Standalone", address, argPriority.getInt(parser), parser.isSet(argSkipReply));
// Connect the screen capturing to flatbuf connection processing
QObject::connect(&xcbWrapper, SIGNAL(sig_screenshot(const Image<ColorRgb> &)), &flatbuf, SLOT(setImage(Image<ColorRgb>)));
// Start the capturing
xcbWrapper.start();
// Start the application
app.exec();
}
}
catch (const std::runtime_error & e)
{
// An error occured. Display error and quit
Error(Logger::getInstance("XCBGRABBER"), "%s", e.what());
return -1;
}
return 0;
}

View File

@ -95,6 +95,10 @@ if (ENABLE_X11)
target_link_libraries(hyperiond x11-grabber)
endif ()
if (ENABLE_XCB)
target_link_libraries(hyperiond xcb-grabber)
endif ()
if (ENABLE_QT)
target_link_libraries(hyperiond qt-grabber)
endif ()

View File

@ -75,6 +75,7 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bo
, _v4l2Grabber(nullptr)
, _dispmanx(nullptr)
, _x11Grabber(nullptr)
, _xcbGrabber(nullptr)
, _amlGrabber(nullptr)
, _fbGrabber(nullptr)
, _osxGrabber(nullptr)
@ -133,7 +134,7 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bo
connect(this, &HyperionDaemon::videoMode, _instanceManager, &HyperionIManager::newVideoMode);
// ---- grabber -----
#if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_AMLOGIC) && !defined(ENABLE_QT)
#if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_XCB) && !defined(ENABLE_AMLOGIC) && !defined(ENABLE_QT)
Warning(_log, "No platform capture can be instantiated, because all grabbers have been left out from the build");
#endif
@ -374,7 +375,13 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type &settingsType, co
QByteArray envDisplay = qgetenv("DISPLAY");
if ( !envDisplay.isEmpty() )
{
#if defined(ENABLE_X11)
type = "x11";
#elif defined(ENABLE_XCB)
type = "xcb";
#else
type = "qt";
#endif
}
// qt -> if nothing other applies
else
@ -429,6 +436,14 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type &settingsType, co
_x11Grabber = nullptr;
}
#endif
#ifdef ENABLE_XCB
if(_xcbGrabber != nullptr)
{
_xcbGrabber->stop();
delete _xcbGrabber;
_xcbGrabber = nullptr;
}
#endif
#ifdef ENABLE_QT
if(_qtGrabber != nullptr)
{
@ -479,6 +494,14 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type &settingsType, co
_x11Grabber->tryStart();
#endif
}
else if (type == "xcb")
{
if (_xcbGrabber == nullptr)
createGrabberXcb(grabberConfig);
#ifdef ENABLE_XCB
_xcbGrabber->tryStart();
#endif
}
else if (type == "qt")
{
if (_qtGrabber == nullptr)
@ -603,6 +626,25 @@ void HyperionDaemon::createGrabberX11(const QJsonObject &grabberConfig)
#endif
}
void HyperionDaemon::createGrabberXcb(const QJsonObject &grabberConfig)
{
#ifdef ENABLE_XCB
_xcbGrabber = new XcbWrapper(
_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom,
grabberConfig["pixelDecimation"].toInt(8),
_grabber_frequency);
_xcbGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom);
// connect to HyperionDaemon signal
connect(this, &HyperionDaemon::videoMode, _xcbGrabber, &XcbWrapper::setVideoMode);
connect(this, &HyperionDaemon::settingsChanged, _xcbGrabber, &XcbWrapper::handleSettingsUpdate);
Info(_log, "XCB grabber created");
#else
Error(_log, "The XCB grabber can not be instantiated, because it has been left out from the build");
#endif
}
void HyperionDaemon::createGrabberQt(const QJsonObject &grabberConfig)
{
#ifdef ENABLE_QT

View File

@ -40,6 +40,12 @@
typedef QObject X11Wrapper;
#endif
#ifdef ENABLE_XCB
#include <grabber/XcbWrapper.h>
#else
typedef QObject XcbWrapper;
#endif
#ifdef ENABLE_QT
#include <grabber/QtWrapper.h>
#else
@ -144,6 +150,7 @@ private:
void createGrabberFramebuffer(const QJsonObject & grabberConfig);
void createGrabberOsx(const QJsonObject & grabberConfig);
void createGrabberX11(const QJsonObject & grabberConfig);
void createGrabberXcb(const QJsonObject & grabberConfig);
void createGrabberQt(const QJsonObject & grabberConfig);
void createCecHandler();
@ -159,6 +166,7 @@ private:
V4L2Wrapper* _v4l2Grabber;
DispmanxWrapper* _dispmanx;
X11Wrapper* _x11Grabber;
XcbWrapper* _xcbGrabber;
AmlogicWrapper* _amlGrabber;
FramebufferWrapper* _fbGrabber;
OsxWrapper* _osxGrabber;

View File

@ -105,13 +105,21 @@ QCoreApplication* createApplication(int &argc, char *argv[])
if (!forceNoGui)
{
// if x11, then test if xserver is available
#ifdef ENABLE_X11
#if defined(ENABLE_X11)
Display* dpy = XOpenDisplay(NULL);
if (dpy != NULL)
{
XCloseDisplay(dpy);
isGuiApp = true;
}
#elif defined(ENABLE_XCB)
int screen_num;
xcb_connection_t * connection = xcb_connect(nullptr, &screen_num);
if (!xcb_connection_has_error(connection))
{
isGuiApp = true;
}
xcb_disconnect(connection);
#endif
}
#endif