diff --git a/.gitmodules b/.gitmodules index e3d011f2..981fff4d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,4 +4,4 @@ branch = master [submodule "dependencies/external/flatbuffers"] path = dependencies/external/flatbuffers - url = git://github.com/google/flatbuffers.git + url = https://github.com/google/flatbuffers diff --git a/.travis.yml b/.travis.yml index 6e2a6189..0500b625 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,20 @@ cache: notifications: email: false language: cpp +services: + - docker matrix: include: - os: linux dist: trusty - sudo: required + env: + - DOCKER_TAG=ubuntu1604 + - DOCKER_NAME="Ubuntu 16.04" + - os: linux + dist: trusty + env: + - DOCKER_TAG=cross-qemu-rpistretch + - DOCKER_NAME="Raspberry Pi" - os: osx osx_image: xcode7.3 env: @@ -18,6 +27,5 @@ before_install: - ./.travis/travis_install.sh script: - ./.travis/travis_build.sh - - ./test/testrunner.sh after_success: - ./.travis/travis_deploy.sh diff --git a/.travis/travis_build.sh b/.travis/travis_build.sh index e1f864d0..ddeb569c 100755 --- a/.travis/travis_build.sh +++ b/.travis/travis_build.sh @@ -5,6 +5,7 @@ PLATFORM=x86 BUILD_TYPE=Debug +PACKAGES="" # Detect number of processor cores # default is 4 jobs @@ -16,32 +17,48 @@ elif [[ "$TRAVIS_OS_NAME" == 'linux' ]] then JOBS=$(nproc) fi +echo "compile jobs: ${JOBS:=4}" -# compile prepare -mkdir build || exit 1 -cd build - -# Compile hyperion for tags +# Determine cmake build type; tag builds are Release, else Debug [ -n "${TRAVIS_TAG:-}" ] && BUILD_TYPE=Release -# Compile hyperion for cron - take default settings +# Determine package creation; True for cron and tag builds +[ "${TRAVIS_EVENT_TYPE:-}" == 'cron' ] || [ -n "${TRAVIS_TAG:-}" ] && PACKAGES=package -# Compile for PR (no tag and no cron) +# Determie -dev appends to platform; [ "${TRAVIS_EVENT_TYPE:-}" != 'cron' -a -z "${TRAVIS_TAG:-}" ] && PLATFORM=${PLATFORM}-dev -cmake -DPLATFORM=$PLATFORM -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=/usr .. || exit 2 -if [[ "$TRAVIS_OS_NAME" == 'linux' ]] +# Build the package on osx +if [[ "$TRAVIS_OS_NAME" == 'osx' || "$TRAVIS_OS_NAME" == 'darwin' ]] then - # activate dispmanx and osx mocks - cmake -DENABLE_OSX=ON -DENABLE_DISPMANX=ON .. || exit 5 + # compile prepare + mkdir build || exit 1 + cd build + cmake -DPLATFORM=$PLATFORM -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=/usr .. || exit 2 + make -j ${JOBS} || exit 3 fi -echo "compile jobs: ${JOBS:=4}" -make -j ${JOBS} || exit 3 - -# Build the package on Linux +# Build the package with docker if [[ $TRAVIS_OS_NAME == 'linux' ]] then - make -j ${JOBS} package || exit 4 -fi + echo "Compile Hyperion with DOCKER_TAG = ${DOCKER_TAG} and friendly name DOCKER_NAME = ${DOCKER_NAME}" + # take ownership of deploy dir + mkdir $TRAVIS_BUILD_DIR/deploy + # run docker + docker run --rm \ + -v "${TRAVIS_BUILD_DIR}/deploy:/deploy" \ + -v "${TRAVIS_BUILD_DIR}:/source:ro" \ + hyperionorg/hyperion-ci:$DOCKER_TAG \ + /bin/bash -c "mkdir build && cp -r /source/. /build && + cd /build && mkdir build && cd build && + cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} .. || exit 2 && + make -j $(nproc) ${PACKAGES} || exit 3 && + echo '---> Copy binaries and packages to host folder: ${TRAVIS_BUILD_DIR}/deploy' && + cp -v /build/build/bin/h* /deploy/ 2>/dev/null || : && + cp -v /build/build/Hyperion-* /deploy/ 2>/dev/null || : && + exit 0; + exit 1 " || { echo "---> Hyperion compilation failed! Abort"; exit 4; } + # overwrite file owner to current user + sudo chown -fR $(stat -c "%U:%G" $TRAVIS_BUILD_DIR/deploy) $TRAVIS_BUILD_DIR/deploy +fi diff --git a/.travis/travis_deploy.sh b/.travis/travis_deploy.sh index 311727e6..2f41c285 100755 --- a/.travis/travis_deploy.sh +++ b/.travis/travis_deploy.sh @@ -1,10 +1,13 @@ #!/bin/bash -# sf_upload +# sf_upload sf_upload() { +echo "Uploading following files: ${1} +to dir /hyperion-project/${2}" + /usr/bin/expect <<-EOD - spawn scp $1 hyperionsf37@frs.sourceforge.net:/home/frs/project/hyperion-project/dev/$2 + spawn scp $1hyperionsf37@frs.sourceforge.net:/home/frs/project/hyperion-project/$2 expect "*(yes/no)*" send "yes\r" expect "*password:*" @@ -13,18 +16,49 @@ sf_upload() EOD } -deploylist="hyperion-2.0.0-Linux-x86.tar.gz" +# append current Date to filename (just packages no binaries) +appendDate() +{ + D=$(date +%Y-%m-%d) + for F in $TRAVIS_BUILD_DIR/deploy/Hy* + do + mv "$F" "${F%.*}-$D.${F##*.}" + done +} + +# append friendly name (just packages no binaries) +appendName() +{ + for F in $TRAVIS_BUILD_DIR/deploy/Hy* + do + mv "$F" "${F%.*}-($DOCKER_NAME).${F##*.}" + done +} + +# get all files to deploy (just packages no binaries) +getFiles() +{ + FILES="" + for f in $TRAVIS_BUILD_DIR/deploy/Hy*; + do FILES+="${f} "; + done; +} if [[ $TRAVIS_OS_NAME == 'linux' ]]; then - cd $TRAVIS_BUILD_DIR/build if [[ -n $TRAVIS_TAG ]]; then echo "tag upload" - sf_upload $deploylist release + appendName + appendDate + getFiles + sf_upload $FILES release elif [[ $TRAVIS_EVENT_TYPE == 'cron' ]]; then echo "cron upload" - sf_upload $deploylist alpha + appendName + appendDate + getFiles + sf_upload $FILES dev/alpha else - echo "PR can't be uploaded for security reasons" - sf_upload $deploylist pr + echo "Direct pushed no upload, PRs not possible" + #sf_upload $FILES pr fi fi diff --git a/.travis/travis_install.sh b/.travis/travis_install.sh index e974ab2e..98c88380 100755 --- a/.travis/travis_install.sh +++ b/.travis/travis_install.sh @@ -18,8 +18,8 @@ then elif [[ $TRAVIS_OS_NAME == 'linux' ]] then echo "Install linux deps" - sudo apt-get -qq update - sudo apt-get install -qq -y qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev doxygen expect + #sudo apt-get -qq update + #sudo apt-get install -qq -y qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python3-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev doxygen expect else echo "Unsupported platform: $TRAVIS_OS_NAME" exit 5 diff --git a/CMakeLists.txt b/CMakeLists.txt index 6248c3d4..6badb8ad 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,7 @@ SET ( DEFAULT_AMLOGIC OFF ) SET ( DEFAULT_DISPMANX OFF ) SET ( DEFAULT_OSX OFF ) SET ( DEFAULT_X11 OFF ) +SET ( DEFAULT_QT ON ) SET ( DEFAULT_WS281XPWM OFF ) SET ( DEFAULT_USE_SHARED_AVAHI_LIBS ON ) SET ( DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS OFF ) @@ -151,7 +152,8 @@ message(STATUS "ENABLE_USB_HID = ${ENABLE_USB_HID}") option(ENABLE_X11 "Enable the X11 grabber" ${DEFAULT_X11}) message(STATUS "ENABLE_X11 = ${ENABLE_X11}") -SET(ENABLE_QT5 ON) +option(ENABLE_QT "Enable the qt grabber" ${DEFAULT_QT}) +message(STATUS "ENABLE_QT = ${ENABLE_QT}") option(ENABLE_TESTS "Compile additional test applications" ${DEFAULT_TESTS}) message(STATUS "ENABLE_TESTS = ${ENABLE_TESTS}") @@ -159,7 +161,6 @@ message(STATUS "ENABLE_TESTS = ${ENABLE_TESTS}") option(ENABLE_PROFILER "enable profiler capabilities - not for release code" OFF) message(STATUS "ENABLE_PROFILER = ${ENABLE_PROFILER}") - SET ( FLATBUFFERS_INSTALL_BIN_DIR ${CMAKE_BINARY_DIR}/flatbuf ) SET ( FLATBUFFERS_INSTALL_LIB_DIR ${CMAKE_BINARY_DIR}/flatbuf ) diff --git a/CompileHowto.md b/CompileHowto.md index dcbb869d..b0c49cb0 100644 --- a/CompileHowto.md +++ b/CompileHowto.md @@ -1,4 +1,18 @@ -# Install the required tools and dependencies +# With Docker +If you are using [Docker](https://www.docker.com/), you can compile Hyperion inside a docker container. This keeps your system clean and with a simple script it's easy to use. Supported is also cross compilation for Raspberry Pi (Raspbian stretch) + +To compile Hyperion for Ubuntu 16.04 (x64) or higher just execute the following command +``` +wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/docker-compile.sh && chmod +x *.sh && ./docker-compile.sh +``` +To compile Hyperion for Raspberry Pi +``` +wget -qN https://raw.github.com/hyperion-project/hyperion.ng/master/bin/scripts/docker-compile.sh && chmod +x *.sh && ./docker-compile.sh -t cross-qemu-rpistretch +``` +The compiled binaries and packages will be available at the deploy folder next to the script +Note: call the script with `./docker-compile.sh -h` for more options + +# The usual way ## Debian/Ubuntu/Win10LinuxSubsystem @@ -60,12 +74,12 @@ sudo make install/strip sudo make uninstall # ... or run it from compile directory bin/hyperiond -# webui is located on localhost:8099 +# webui is located on localhost:8090 or 8091 ``` ### Download - Create hyperion directory and checkout the code from github + Creates hyperion directory and checkout the code from github You might want to add `--depth 1` to the `git` command if you only want to compile the current source and have no need for the entire git repository @@ -74,7 +88,7 @@ export HYPERION_DIR="hyperion" git clone --recursive https://github.com/hyperion-project/hyperion.ng.git "$HYPERION_DIR" ``` -**Note:** If you forget the --recursive in above statement or you are updating an existing clone you need to clone the protobuf submodule by runnning the follwing two statements: +**Note:** If you forget the --recursive in above statement or you are updating an existing clone you need to clone the flatbuffers submodule by runnning the follwing two statements: ``` git submodule init git submodule update diff --git a/CrossCompileHowto.txt b/CrossCompileHowto.txt index 73d565da..82adad84 100644 --- a/CrossCompileHowto.txt +++ b/CrossCompileHowto.txt @@ -12,7 +12,7 @@ sudo apt-get update sudo apt-get upgrade #TO-DO verify what is really required -#blacklist: protobuf-compiler lib32z1 lib32ncurses5 lib32bz2-1.0 zlib1g-dev +#blacklist: lib32z1 lib32ncurses5 lib32bz2-1.0 zlib1g-dev sudo apt-get -qq -y install git rsync cmake build-essential qtbase5-dev libqt5serialport5-dev libusb-1.0-0-dev python-dev libxrender-dev libavahi-core-dev libavahi-compat-libdnssd-dev echo 'PATH=$PATH:$HOME/raspberrypi/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian/bin' >> .bashrc @@ -58,12 +58,12 @@ git clone --depth 1 git://github.com/raspberrypi/tools.git "$RASCROSS_DIR/tools" # get the Hyperion sources git clone --recursive https://github.com/hyperion-project/hyperion.ng.git "$HYPERION_DIR" -# do a native build (to build the protobuf compiler for the native platform) +# do a native build (to build the flatbuffers compiler for the native platform) mkdir -p "$NATIVE_BUILD_DIR" cmake -DENABLE_DISPMANX=OFF --build "$NATIVE_BUILD_DIR" "$HYPERION_DIR" # do the cross build -# specify the protoc export file to import the protobuf compiler from the native build +# specify the protoc export file to import the flatbuffers compiler from the native build mkdir -p "$TARGET_BUILD_DIR" cmake -DCMAKE_TOOLCHAIN_FILE="$TOOLCHAIN_FILE" -DIMPORT_PROTOC=$NATIVE_BUILD_DIR/protoc_export.cmake --build "$TARGET_BUILD_DIR" "$HYPERION_DIR" diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index e23b46d1..ddb8874e 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 qt grabber +#cmakedefine ENABLE_QT + // Define to enable the spi-device #cmakedefine ENABLE_SPIDEV @@ -42,5 +45,3 @@ #define HYPERION_VERSION "${HYPERION_VERSION_MAJOR}.${HYPERION_VERSION_MINOR}.${HYPERION_VERSION_PATCH}" #define HYPERION_JSON_VERSION "1.0.0" - - diff --git a/assets/webconfig/i18n/de.json b/assets/webconfig/i18n/de.json index f72243e0..b533fd60 100644 --- a/assets/webconfig/i18n/de.json +++ b/assets/webconfig/i18n/de.json @@ -285,6 +285,7 @@ "InfoDialog_nowrite_text" : "Hyperion hat keinen Schreibzugriff auf die aktuell geladene Konfiguration. Bitte korrigiere die Dateizugriffsrechte um fortzufahren.", "InfoDialog_nowrite_foottext" : "Die Webkonfiguration wird automatisch wieder freigegeben, sobald das Problem behoben wurde!", "infoDialog_wizrgb_text" : "Deine RGB Byte Reihenfolge ist bereits richtig eingestellt.", + "infoDialog_writeimage_error_text": "Die ausgewählte Datei \"$1\" ist keine Bilddatei oder ist beschädigt! Bitte wähle eine andere Bilddatei aus.", "infoDialog_writeconf_error_text" : "Das speichern der Konfiguration ist fehlgeschlagen.", "infoDialog_import_jsonerror_text" : "Die ausgewählte Konfigurations-Datei \"$1\" ist keine .json Datei oder ist beschädigt! Fehlermeldung: ($2)", "infoDialog_import_hyperror_text" : "Die ausgewählte Konfigurations-Datei \"$1\" kann nicht importiert werden. Sie ist nicht kompatibel mit Hyperion 2.0 und höher!", diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 693a248e..ef297955 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -285,6 +285,7 @@ "InfoDialog_nowrite_text" : "Hyperion can't write to your current loaded configuration file. Please repair the file permissions to proceed.", "InfoDialog_nowrite_foottext" : "The WebUI will be unlocked automatically after you solved the problem!", "infoDialog_wizrgb_text" : "Your RGB Byte Order is already well adjusted.", + "infoDialog_writeimage_error_text": "The selected file \"$1\" is no image file or it's corrupted! Please select another image file.", "infoDialog_writeconf_error_text" : "Saving your configuration failed.", "infoDialog_import_jsonerror_text" : "The selected configuration file \"$1\" is no .json file or it's corrupted. Error message: ($2)", "infoDialog_import_hyperror_text" : "The selected configuration file \"$1\" can't be imported. It's not compatible with Hyperion 2.0 and higher!", diff --git a/assets/webconfig/i18n/qqq.json b/assets/webconfig/i18n/qqq.json index 9722d677..40ac58cc 100644 --- a/assets/webconfig/i18n/qqq.json +++ b/assets/webconfig/i18n/qqq.json @@ -1,10 +1,10 @@ { "@metadata": { "authors": [ - "brindosch" + "brindosch, paulchen-panther" ], "project" : "Hyperion WebUI string docu", - "last-updated": "2016-11-30" + "last-updated": "2019-01-02" }, "edt_msg_error_notset" : " When a property is not set", "edt_msg_error_notempty" : "When a string must not be empty", @@ -40,8 +40,14 @@ "edt_msg_button_add_row_title" : "Title on Add Row buttons. $1 = This key takes one variable: The title of object to add", "edt_msg_button_move_down_title" : "Title on Move Down buttons", "edt_msg_button_move_up_title" : "Title on Move Up buttons", - "edt_msg_button_delete_row_titlet" : "Title on Delete Row buttons. $1 = This key takes one variable: The title of object to delete", + "edt_msg_button_delete_row_title" : "Title on Delete Row buttons. $1 = This key takes one variable: The title of object to delete", "edt_msg_button_delete_row_title_short" : "Title on Delete Row buttons, short version (no parameter with the object title)", "edt_msg_button_collapse" : "Title on Collapse buttons", - "edt_msg_button_expand" : "Title on Expand buttons" -} \ No newline at end of file + "edt_msg_button_expand" : "Title on Expand buttons", + "edt_msg_error_date" : "When a date is in incorrect format. $1 = This key takes one variable: The valid format", + "edt_msg_error_time" : "When a time is in incorrect format. $1 = This key takes one variable: The valid format", + "edt_msg_error_datetime_local" : "When a datetime-local is in incorrect format. $1 = This key takes one variable: The valid format", + "edt_msg_error_invalid_epoch" : "When a integer date is less than 1 January 1970", + "edt_msg_flatpickr_toggle_button" : "Title on Flatpickr toggle buttons", + "edt_msg_flatpickr_clear_button" : "Title on Flatpickr clear buttons" +} diff --git a/assets/webconfig/js/content_effectsconfigurator.js b/assets/webconfig/js/content_effectsconfigurator.js index 25adc2ce..6b3411d9 100644 --- a/assets/webconfig/js/content_effectsconfigurator.js +++ b/assets/webconfig/js/content_effectsconfigurator.js @@ -2,6 +2,7 @@ $(document).ready( function() { performTranslation(); var oldDelList = []; var effectName = ""; + var imageData = ""; var effects_editor = null; var effectPy = ""; var testrun; @@ -31,9 +32,30 @@ $(document).ready( function() { function triggerTestEffect() { testrun = true; var args = effects_editor.getEditor('root.args'); - requestTestEffect(effectName, ":/effects/" + effectPy.slice(1), JSON.stringify(args.getValue())); + requestTestEffect(effectName, ":/effects/" + effectPy.slice(1), JSON.stringify(args.getValue()), imageData); }; + // Specify upload handler for image files + JSONEditor.defaults.options.upload = function(type, file, cbs) { + var fileReader = new FileReader(); + + //check file + if (!file.type.startsWith('image')) { + imageData = ""; + cbs.failure('File upload error'); + // TODO clear file dialog. + showInfoDialog('error', "", $.i18n('infoDialog_writeimage_error_text', file.name)); + return; + } + + fileReader.onload = function () { + imageData = this.result.split(',')[1]; + console.log(imageData); + cbs.success(file.name); + }; + + fileReader.readAsDataURL(file); + }; $("#effectslist").off().on("change", function(event) { if(effects_editor != null) @@ -48,6 +70,7 @@ $(document).ready( function() { effectPy = ':'; effectPy += effects[idx].schemaContent.script; + imageData = ""; $("#name-input").trigger("change"); $("#eff_desc").html(createEffHint($.i18n(effects[idx].schemaContent.title),$.i18n(effects[idx].schemaContent.title+'_desc'))); @@ -84,7 +107,7 @@ $(document).ready( function() { // Save Effect $('#btn_write').off().on('click',function() { - requestWriteEffect(effectName,effectPy,JSON.stringify(effects_editor.getValue())); + requestWriteEffect(effectName,effectPy,JSON.stringify(effects_editor.getValue()),imageData); $(hyperion).one("cmd-create-effect", function(event) { if (event.response.success) showInfoDialog('success', "", $.i18n('infoDialog_effconf_created_text', effectName)); @@ -163,9 +186,9 @@ $(document).ready( function() { //create basic effect list var effects = serverSchema.properties.effectSchemas.internal for(var idx=0; idxSize: '+file.size+' bytes'; - if(mime.substr(0,5)==="image") { - this.preview.innerHTML += '
'; - var img = document.createElement('img'); - img.style.maxWidth = '100%'; - img.style.maxHeight = '100px'; - img.src = this.preview_value; - this.preview.appendChild(img); - } - - this.preview.innerHTML += '
'; var uploadButton = this.getButton('Upload', 'upload', 'Upload'); this.preview.appendChild(uploadButton); uploadButton.addEventListener('click',function(event) { @@ -6036,6 +6020,11 @@ JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({ } }); }); + + if(this.jsoneditor.options.auto_upload || this.schema.options.auto_upload) { + uploadButton.dispatchEvent(new MouseEvent('click')); + this.preview.removeChild(uploadButton); + } }, enable: function() { if(this.uploader) this.uploader.disabled = false; @@ -6825,10 +6814,7 @@ JSONEditor.defaults.themes.bootstrap3 = JSONEditor.AbstractTheme.extend({ }, getButton: function(text, icon, title) { var el = this._super(text, icon, title); - if(icon.className.includes("fa-times")) - el.className += 'btn btn-sm btn-danger'; - else - el.className += 'btn btn-sm btn-primary'; + el.className += 'btn btn-default'; return el; }, getTable: function() { diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js index 70bd8a3c..4b2c914f 100644 --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -316,8 +316,6 @@ function createJsonEditor(container,schema,setconfig,usePanel,arrayre) $('#'+container).off(); $('#'+container).html(""); - //JSONEditor.plugins.selectize.enable = true; - if (typeof arrayre === 'undefined') arrayre = true; diff --git a/bin/scripts/docker-compile.sh b/bin/scripts/docker-compile.sh new file mode 100644 index 00000000..55f2f763 --- /dev/null +++ b/bin/scripts/docker-compile.sh @@ -0,0 +1,112 @@ +#!/bin/bash -e + +DOCKER="docker" +# Git repo url of Hyperion +GIT_REPO_URL="https://github.com/hyperion-project/hyperion.ng.git" +# cmake build type +BUILD_TYPE="Release" +# the image tag at hyperionorg/hyperion-ci +BUILD_TARGET="ubuntu1604" +# build packages (.deb .zip ...) +BUILD_PACKAGES=true +# packages string inserted to cmake cmd +PACKAGES="" + +# get current path to this script, independent of calling +pushd . > /dev/null +SCRIPT_PATH="${BASH_SOURCE[0]}" +if ([ -h "${SCRIPT_PATH}" ]); then + while([ -h "${SCRIPT_PATH}" ]); do cd `dirname "$SCRIPT_PATH"`; + SCRIPT_PATH=`readlink "${SCRIPT_PATH}"`; done +fi +cd `dirname ${SCRIPT_PATH}` > /dev/null +SCRIPT_PATH=`pwd`; +popd > /dev/null + +set +e +$DOCKER ps >/dev/null 2>&1 +if [ $? != 0 ]; then + DOCKER="sudo docker" +fi +# check if docker is available +if ! $DOCKER ps >/dev/null; then + echo "Error connecting to docker:" + $DOCKER ps + printHelp + exit 1 +fi +set -e + +# help print function +function printHelp { +echo "######################################################## +## A script to compile Hyperion inside a docker container +## Requires installed Docker: https://www.docker.com/ +## Without arguments it will compile Hyperion for Ubuntu 16.04 (x64) or higher. +## Supports Raspberry Pi (armv6) cross compilation (Raspbian Stretch) +## +## Homepage: https://www.hyperion-project.org +## Forum: https://forum.hyperion-project.org +######################################################## +# These are possible arguments to modify the script behaviour with their default values +# +# docker-compile.sh -h # Show this help message +# docker-compile.sh -t ubuntu1604 # The docker tag, one of ubuntu1604 | cross-qemu-rpistretch +# docker-compile.sh -b Release # cmake Release or Debug build +# docker-compile.sh -p true # If true build packages with CPack +# More informations to docker tags at: https://hub.docker.com/r/hyperionorg/hyperion-ci/" +} + +while getopts t:b:p:h option +do + case "${option}" + in + t) BUILD_TARGET=${OPTARG};; + b) BUILD_TYPE=${OPTARG};; + p) BUILD_PACKAGES=${OPTARG};; + h) printHelp; exit 0;; + esac +done + +# determine package creation +if [ $BUILD_PACKAGES == "true" ]; then + PACKAGES="package" +fi + +echo "---> Initilize with BUILD_TARGET=${BUILD_TARGET}, BUILD_TYPE=${BUILD_TYPE}, BUILD_PACKAGES=${BUILD_PACKAGES}" + +# cleanup deploy folder, create folder for ownership +sudo rm -fr $SCRIPT_PATH/deploy >/dev/null 2>&1 +mkdir $SCRIPT_PATH/deploy >/dev/null 2>&1 + +# get Hyperion source, cleanup previous folder +echo "---> Downloading Hyperion source code from ${GIT_REPO_URL}" +sudo rm -fr $SCRIPT_PATH/hyperion >/dev/null 2>&1 +git clone --recursive --depth 1 -q -b rework $GIT_REPO_URL $SCRIPT_PATH/hyperion || { echo "---> Failed to download Hyperion source code! Abort"; exit 1; } + +# start compilation +# Remove container after stop +# Mount /deploy to /deploy +# Mount source dir to /source +# Target docker image +# execute inside container all commands on bash +echo "---> Startup docker..." +$DOCKER run --rm \ + -v "${SCRIPT_PATH}/deploy:/deploy" \ + -v "${SCRIPT_PATH}/hyperion:/source:ro" \ + hyperionorg/hyperion-ci:$BUILD_TARGET \ + /bin/bash -c "mkdir build && cp -r /source/. /build && + cd /build && mkdir build && cd build && + cmake -DCMAKE_BUILD_TYPE=${BUILD_TYPE} .. || exit 2 && + make -j $(nproc) ${PACKAGES} || exit 3 && + echo '---> Copy binaries and packages to host folder: ${SCRIPT_PATH}/deploy' && + cp -v /build/build/bin/h* /deploy/ 2>/dev/null || : && + cp -v /build/build/Hyperion-* /deploy/ 2>/dev/null || : && + exit 0; + exit 1 " || { echo "---> Hyperion compilation failed! Abort"; exit 4; } + +# overwrite file owner to current user +sudo chown -fR $(stat -c "%U:%G" $SCRIPT_PATH/deploy) $SCRIPT_PATH/deploy + +echo "---> Script finished, view folder ${SCRIPT_PATH}/deploy for compiled packages and binaries" +exit 0 diff --git a/bin/scripts/setup_hyperion_forward.sh b/bin/scripts/setup_hyperion_forward.sh deleted file mode 100644 index 4fe45218..00000000 --- a/bin/scripts/setup_hyperion_forward.sh +++ /dev/null @@ -1,165 +0,0 @@ -#!/bin/bash -# Script to add a second or more hyperion instance(s) to the corresponding system service - -# Make sure /sbin is on the path (for service to find sub scripts) -PATH="/sbin:$PATH" - -#Check, if script is running as root -if [ $(id -u) != 0 ]; then - echo '---> Critical Error: Please run the script as root (sudo sh ./setup_hyperion_forward.sh) -> abort' - exit 1 -fi - -#Welcome message -echo '*******************************************************************************' -echo 'This setup script will duplicate the hyperion service' -echo 'Choose the name(s) for one or more config files - one service for each config' -echo 'Created by brindosch - hyperion-project.org - the official Hyperion source.' -echo '*******************************************************************************' - -#Prompt for confirmation to proceed -while true -do -echo -n "---> Do you really want to proceed? (y or n) :" -read CONFIRM -case $CONFIRM in -y|Y|YES|yes|Yes) break ;; -n|N|no|NO|No) -echo "---> Aborting - you entered \"$CONFIRM\"" -exit -;; -*) echo "-> Please enter only y or n" -esac -done -echo "---> You entered \"$CONFIRM\". We will proceed!" -echo "" - -#Check which system we are on -OS_OPENELEC=`grep -m1 -c 'OpenELEC\|RasPlex\|LibreELEC' /etc/issue` -USE_SYSTEMD=`grep -m1 -c systemd /proc/1/comm` -USE_INITCTL=`which /sbin/initctl | wc -l` -USE_SERVICE=`which /usr/sbin/service | wc -l` - -#Setting up the paths to service files -if [ $USE_INITCTL -eq 1 ]; then - SERVICEPATH=/etc/init -elif [ $OS_OPENELEC -eq 1 ]; then - SERVICEPATH=/storage/.config -elif [ $USE_SYSTEMD -eq 1 ]; then - SERVICEPATH=/etc/systemd/system -elif [ $USE_SERVICE -eq 1 ]; then - SERVICEPATH/etc/init.d -fi - -#Setting up the default PROTO/JSON ports -JSONPORT=19444 -PROTOPORT=19445 -# and service count -SERVICEC=1 - -#Setting up the paths to config files -if [ $OS_OPENELEC -eq 1 ]; then - CONFIGPATH=/storage/.config -else CONFIGPATH=/opt/hyperion/config -fi - -#Ask the user for some informations regarding the setup -echo "---> Please enter the config name(s) you want to create" -echo "---> Information: One name creates one service and two names two services etc" -echo '---> Please enter them seperated with a space in a one line row!' -echo '---> example: hyperion.philipshue_1.json hyperion.AtmoOrb_2.json hypthreeconf.json' -echo '---> In any case, add ".json" at the end of each file name' -read -p 'Config file name(s): ' FILENAMES -echo '---> Thank you, we will modify your Hyperion installation now' -sleep 2 - -#Processing input -set $FILENAMES -FWCOUNT=${#} - -#Convert all old config file paths to make sure this script is working (default for new installs with 1.02.0 and higher) -if [ $USE_INITCTL -eq 1 ]; then - sed -i "s|/etc/hyperion.config.json|/etc/hyperion/hyperion.config.json|g" $SERVICEPATH/hyperion.conf -elif [ $OS_OPENELEC -eq 1 ]; then - sleep 0 -elif [ $USE_SYSTEMD -eq 1 ]; then - sed -i "s|/etc/hyperion.config.json|/etc/hyperion/hyperion.config.json|g" $SERVICEPATH/hyperion.service -elif [ $USE_SERVICE -eq 1 ]; then - sed -i "s|/etc/hyperion.config.json|/etc/hyperion/hyperion.config.json|g" $SERVICEPATH/hyperion -fi - -#Processing service files -if [ $USE_INITCTL -eq 1 ]; then - echo "---> Initctl detected, processing service files" - while [ $SERVICEC -le $FWCOUNT ]; do - echo "Processing service ${SERVICEC}: \"hyperion_fw${SERVICEC}.conf\"" - if [ -e "${SERVICEPATH}/hyperion_fw${SERVICEC}.conf" ]; then - echo "Service was already created - skipped" - echo "Input \"${1}\" was skipped" - else - echo "Create ${SERVICEPATH}/hyperion_fw${SERVICEC}.conf" - cp -s $SERVICEPATH/hyperion.conf $SERVICEPATH/hyperion_fw$SERVICEC.conf - echo "Config name changed to \"${1}\" inside \"hyperion_fw${SERVICEC}.conf\"" - sed -i "s/hyperion.config.json/$1/g" $SERVICEPATH/hyperion_fw$SERVICEC.conf - initctl reload-configuration - fi - shift - SERVICEC=$((SERVICEC + 1)) - done -elif [ $OS_OPENELEC -eq 1 ]; then - echo "---> OE/LE detected, processing autostart.sh" - while [ $SERVICEC -le $FWCOUNT ]; do - echo "${SERVICEC}. processing OE autostart.sh entry \"${1}\"" - OE=`grep -m1 -c ${1} $SERVICEPATH/autostart.sh` - if [ $OE -eq 0 ]; then - echo "Add config name \"${1}\" to \"autostart.sh\"" - echo "/storage/hyperion/bin/hyperiond.sh /storage/.config/${1} > /storage/logfiles/hyperion_fw${SERVICEC}.log 2>&1 &" >> /storage/.config/autostart.sh - else - echo "\"${1}\" was already added - skipped" - fi - shift - SERVICEC=$((SERVICEC + 1)) - done -elif [ $USE_SYSTEMD -eq 1 ]; then - echo "---> Systemd detected, processing service files" - while [ $SERVICEC -le $FWCOUNT ]; do - echo "Processing service ${SERVICEC}: \"hyperion_fw${SERVICEC}.service\"" - if [ -e "${SERVICEPATH}/hyperion_fw${SERVICEC}.service" ]; then - echo "Service was already created - skipped" - echo "Input \"${1}\" was skipped" - else - echo "Create ${SERVICEPATH}/hyperion_fw${SERVICEC}.service" - cp -s $SERVICEPATH/hyperion.service $SERVICEPATH/hyperion_fw$SERVICEC.service - echo "Config name changed to \"${1}\" inside \"hyperion_fw${SERVICEC}.service\"" - sed -i "s/hyperion.config.json/$1/g" $SERVICEPATH/hyperion_fw$SERVICEC.service - systemctl -q enable hyperion_fw$SERVICEC.service - fi - shift - SERVICEC=$((SERVICEC + 1)) - done -elif [ $USE_SERVICE -eq 1 ]; then - echo "---> Init.d detected, processing service files" - while [ $SERVICEC -le $FWCOUNT ]; do - echo "Processing service ${SERVICEC}: \"hyperion_fw${SERVICEC}\"" - if [ -e "${SERVICEPATH}/hyperion_fw${SERVICEC}" ]; then - echo "Service was already created - skipped" - echo "Input \"${1}\" was skipped" - else - echo "Create ${SERVICEPATH}/hyperion_fw${SERVICEC}" - cp -s $SERVICEPATH/hyperion $SERVICEPATH/hyperion_fw$SERVICEC - echo "Config name changed to \"${1}\" inside \"hyperion_fw${SERVICEC}\"" - sed -i "s/hyperion.config.json/$1/g" $SERVICEPATH/hyperion_fw$SERVICEC - update-rc.d hyperion_fw$SERVICEC defaults 98 02 - fi - shift - SERVICEC=$((SERVICEC + 1)) - done -fi - -#Service creation done -echo '*******************************************************************************' -echo 'Script done all actions - all input processed' -echo 'Now upload your configuration(s) with HyperCon at the SSH Tab' -echo 'All created Hyperion services will start with your chosen confignames' -echo 'Wiki: wiki.hyperion-project.org Webpage: www.hyperion-project.org' -echo '*******************************************************************************' \ No newline at end of file diff --git a/cmake/packages.cmake b/cmake/packages.cmake index 5ae0d5fe..21b63a74 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -14,7 +14,7 @@ ENDIF() SET ( CPACK_PACKAGE_NAME "Hyperion" ) SET ( CPACK_PACKAGE_DESCRIPTION_SUMMARY "Hyperion is an open source ambient light implementation" ) SET ( CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md" ) -SET ( CPACK_PACKAGE_FILE_NAME "Hyperion-${HYPERION_VERSION_MAJOR}.${HYPERION_VERSION_MINOR}.${HYPERION_VERSION_PATCH}") +SET ( CPACK_PACKAGE_FILE_NAME "Hyperion-${HYPERION_VERSION_MAJOR}.${HYPERION_VERSION_MINOR}.${HYPERION_VERSION_PATCH}-${CMAKE_SYSTEM_NAME}") SET ( CPACK_PACKAGE_CONTACT "packages@hyperion-project.org") SET ( CPACK_PACKAGE_EXECUTABLES "hyperiond;Hyperion" ) SET ( CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/resources/icons/hyperion-icon-32px.png") diff --git a/config/hyperion.config.json.commented b/config/hyperion.config.json.commented index 9532d97c..13e87737 100644 --- a/config/hyperion.config.json.commented +++ b/config/hyperion.config.json.commented @@ -136,7 +136,7 @@ ], /// The configuration for the frame-grabber, contains the following items: - /// * type : type of grabber. (auto|osx|dispmanx|amlogic|x11|framebuffer) [auto] + /// * type : type of grabber. (auto|osx|dispmanx|amlogic|x11|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] @@ -155,9 +155,12 @@ "width" : 96, "height" : 96, - // valid for x11 + // valid for x11|qt "pixelDecimation" : 8, + // valid for qt + "display" 0, + // valid for framebuffer "device" : "/dev/fb0" }, diff --git a/effects/schema/gif.schema.json b/effects/schema/gif.schema.json index 0a61dc11..3161e3ac 100644 --- a/effects/schema/gif.schema.json +++ b/effects/schema/gif.schema.json @@ -3,11 +3,16 @@ "script" : "gif.py", "title":"edt_eff_gif_header", "required":true, - "properties":{ + "properties": { "image": { "type": "string", "title":"edt_eff_image", - "format" : "file", + "format" : "url", + "options" : + { + "upload" : true, + "auto_upload" : true + }, "default": "", "propertyOrder" : 1 }, diff --git a/include/api/JsonAPI.h b/include/api/JsonAPI.h index 7c7caf13..ed239eca 100644 --- a/include/api/JsonAPI.h +++ b/include/api/JsonAPI.h @@ -114,7 +114,7 @@ private: /// /// @param message the incoming message /// - void handleEffectCommand(const QJsonObject & message, const QString &command, const int tan); + void handleEffectCommand(const QJsonObject &message, const QString &command, const int tan); /// /// Handle an incoming JSON Effect message (Write JSON Effect) diff --git a/include/blackborder/BlackBorderDetector.h b/include/blackborder/BlackBorderDetector.h index 7323ebd1..d92edb3c 100644 --- a/include/blackborder/BlackBorderDetector.h +++ b/include/blackborder/BlackBorderDetector.h @@ -89,10 +89,9 @@ namespace hyperion // find first X pixel of the image for (int x = 0; x < width33percent; ++x) { - const Pixel_T & color1 = image( (width - x), yCenter); // right side center line check - const Pixel_T & color2 = image(x, height33percent); - const Pixel_T & color3 = image(x, height66percent); - if (!isBlack(color1) || !isBlack(color2) || !isBlack(color3)) + if (!isBlack(image((width - x), yCenter)) + || !isBlack(image(x, height33percent)) + || !isBlack(image(x, height66percent))) { firstNonBlackXPixelIndex = x; break; @@ -102,10 +101,9 @@ namespace hyperion // find first Y pixel of the image for (int y = 0; y < height33percent; ++y) { - const Pixel_T & color1 = image(xCenter, (height - y)); // bottom center line check - const Pixel_T & color2 = image(width33percent, y ); - const Pixel_T & color3 = image(width66percent, y); - if (!isBlack(color1) || !isBlack(color2) || !isBlack(color3)) + if (!isBlack(image(xCenter, (height - y))) + || !isBlack(image(width33percent, y)) + || !isBlack(image(width66percent, y))) { firstNonBlackYPixelIndex = y; break; @@ -203,10 +201,9 @@ namespace hyperion int x; for (x = 0; x < width33percent; ++x) { - const Pixel_T & color1 = image( (width - x), yCenter); // right side center line check - const Pixel_T & color2 = image(x, height33percent); - const Pixel_T & color3 = image(x, height66percent); - if (!isBlack(color1) || !isBlack(color2) || !isBlack(color3)) + if (!isBlack(image((width - x), yCenter)) + || !isBlack(image(x, height33percent)) + || !isBlack(image(x, height66percent))) { firstNonBlackXPixelIndex = x; break; @@ -216,13 +213,13 @@ namespace hyperion // find first Y pixel of the image for (int y = 0; y < height33percent; ++y) { - const Pixel_T & color1 = image(x, y );// left side top check - const Pixel_T & color2 = image(x, (height - y)); // left side bottom check - const Pixel_T & color3 = image( (width - x), y); // right side top check - const Pixel_T & color4 = image( (width - x), (height - y)); // right side bottom check - if (!isBlack(color1) || !isBlack(color2) || !isBlack(color3) || !isBlack(color4)) + // left side top + left side bottom + right side top + right side bottom + if (!isBlack(image(x, y)) + || !isBlack(image(x, (height - y))) + || !isBlack(image((width - x), y)) + || !isBlack(image((width - x), (height - y)))) { -// std::cout << "y " << y << " lt " << int(isBlack(color1)) << " lb " << int(isBlack(color2)) << " rt " << int(isBlack(color3)) << " rb " << int(isBlack(color4)) << std::endl; +// std::cout << "y " << y << " lt " << int(isBlack(color1)) << " lb " << int(isBlack(color2)) << " rt " << int(isBlack(color3)) << " rb " << int(isBlack(color4)) << std::endl; firstNonBlackYPixelIndex = y; break; } diff --git a/include/effectengine/Effect.h b/include/effectengine/Effect.h index e028a36a..3e786284 100644 --- a/include/effectengine/Effect.h +++ b/include/effectengine/Effect.h @@ -28,7 +28,14 @@ class Effect : public QThread public: friend class EffectModule; - Effect(Hyperion* hyperion, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args = QJsonObject()); + Effect(Hyperion *hyperion + , int priority + , int timeout + , const QString &script + , const QString &name + , const QJsonObject &args = QJsonObject() + , const QString &imageData = "" + ); virtual ~Effect(); virtual void run(); @@ -55,14 +62,14 @@ public: QJsonObject getArgs() const { return _args; } signals: - void setInput(const int priority, const std::vector& ledColors, const int timeout_ms, const bool& clearEffect); - void setInputImage(const int priority, const Image& image, const int timeout_ms, const bool& clearEffect); + void setInput(const int priority, const std::vector &ledColors, const int timeout_ms, const bool &clearEffect); + void setInputImage(const int priority, const Image &image, const int timeout_ms, const bool &clearEffect); private: void addImage(); - Hyperion* _hyperion; + Hyperion *_hyperion; const int _priority; @@ -72,18 +79,19 @@ private: const QString _name; const QJsonObject _args; + const QString _imageData; int64_t _endTime; /// Buffer for colorData QVector _colors; - Logger* _log; + Logger *_log; // Reflects whenever this effects should interupt (timeout or external request) bool _interupt = false; QSize _imageSize; QImage _image; - QPainter* _painter; + QPainter *_painter; QVector _imageStack; }; diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h index c96aff38..8ee1ba87 100644 --- a/include/effectengine/EffectEngine.h +++ b/include/effectengine/EffectEngine.h @@ -74,7 +74,15 @@ public slots: int runEffect(const QString &effectName, int priority, int timeout = -1, const QString &origin="System"); /// Run the specified effect on the given priority channel and optionally specify a timeout - int runEffect(const QString &effectName, const QJsonObject & args, int priority, int timeout = -1, const QString &pythonScript = "", const QString &origin = "System", unsigned smoothCfg=0); + int runEffect(const QString &effectName + , const QJsonObject &args + , int priority + , int timeout = -1 + , const QString &pythonScript = "" + , const QString &origin = "System" + , unsigned smoothCfg=0 + , const QString &imageData = "" + ); /// Clear any effect running on the provided channel void channelCleared(int priority); @@ -92,7 +100,15 @@ private slots: private: /// Run the specified effect on the given priority channel and optionally specify a timeout - int runEffectScript(const QString &script, const QString &name, const QJsonObject & args, int priority, int timeout = -1, const QString & origin="System", unsigned smoothCfg=0); + int runEffectScript(const QString &script + ,const QString &name + , const QJsonObject &args + , int priority + , int timeout = -1 + , const QString &origin="System" + , unsigned smoothCfg=0 + , const QString &imageData = "" + ); private: Hyperion * _hyperion; diff --git a/include/grabber/QtGrabber.h b/include/grabber/QtGrabber.h new file mode 100644 index 00000000..288728c4 --- /dev/null +++ b/include/grabber/QtGrabber.h @@ -0,0 +1,96 @@ +#pragma once + +#include + +// Hyperion-utils includes +#include +#include + +class QScreen; + +/// +/// @brief The platform capture implementation based on QT API +/// +class QtGrabber : public Grabber +{ +public: + + QtGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display); + + virtual ~QtGrabber(); + + /// + /// Captures a single snapshot of the display and writes the data to the given image. The + /// provided image should have the same dimensions as the configured values (_width and + /// _height) + /// + /// @param[out] image The snapped screenshot (should be initialized with correct width and + /// height) + /// + virtual int grabFrame(Image & image); + + /// + /// @brief Set a new video mode + /// + virtual void setVideoMode(VideoMode mode); + + /// + /// @brief Apply new width/height values, overwrite Grabber.h implementation as qt doesn't use width/height, just pixelDecimation to calc dimensions + /// + virtual bool setWidthHeight(int width, int height) { return true; }; + + /// + /// @brief Apply new pixelDecimation + /// + virtual void setPixelDecimation(int pixelDecimation); + + /// + /// Set the crop values + /// @param cropLeft Left pixel crop + /// @param cropRight Right pixel crop + /// @param cropTop Top pixel crop + /// @param cropBottom Bottom pixel crop + /// + virtual void setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom); + + /// + /// @brief Apply display index + /// + virtual void setDisplayIndex(int index); + +private slots: + /// + /// @brief is called whenever the current _screen changes it's geometry + /// @param geo The new geometry + /// + void geometryChanged(const QRect &geo); + +private: + /// + /// @brief Setup a new capture display, will free the previous one + /// @return True on success, false if no display is found + /// + const bool setupDisplay(); + + /// + /// @brief Is called whenever we need new screen dimension calculations based on window geometry + /// + int updateScreenDimensions(const bool& force); + + /// + /// @brief free the _screen pointer + /// + void freeResources(); + +private: + + unsigned _display; + int _pixelDecimation; + unsigned _screenWidth; + unsigned _screenHeight; + unsigned _src_x; + unsigned _src_y; + unsigned _src_x_max; + unsigned _src_y_max; + QScreen* _screen; +}; diff --git a/include/grabber/QtWrapper.h b/include/grabber/QtWrapper.h new file mode 100644 index 00000000..3c4b7d28 --- /dev/null +++ b/include/grabber/QtWrapper.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +/// +/// The QtWrapper uses QtFramework API's to get a picture from system +/// +class QtWrapper: public GrabberWrapper +{ +public: + /// + /// Constructs the framebuffer frame grabber with a specified grab size and update rate. + /// + /// @param[in] cropLeft Remove from left [pixels] + /// @param[in] cropRight Remove from right [pixels] + /// @param[in] cropTop Remove from top [pixels] + /// @param[in] cropBottom Remove from bottom [pixels] + /// @param[in] pixelDecimation Decimation factor for image [pixels] + /// @param[in] updateRate_Hz The image grab rate [Hz] + /// + QtWrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display, const unsigned updateRate_Hz); + + /// + /// Destructor of this qt frame grabber. Releases any claimed resources. + /// + virtual ~QtWrapper() {}; + +public slots: + /// + /// Performs a single frame grab and computes the led-colors + /// + virtual void action(); + +private: + /// The actual grabber + QtGrabber _grabber; +}; diff --git a/include/hyperion/Grabber.h b/include/hyperion/Grabber.h index 16e2ee5e..5e3b2359 100644 --- a/include/hyperion/Grabber.h +++ b/include/hyperion/Grabber.h @@ -39,7 +39,7 @@ public: virtual bool setWidthHeight(int width, int height); /// - /// @brief Apply new pixelDecimation (used from x11) + /// @brief Apply new pixelDecimation (used from x11 and qt) /// virtual void setPixelDecimation(int pixelDecimation) {}; @@ -71,7 +71,7 @@ public: virtual void setDeviceVideoStandard(QString device, VideoStandard videoStandard) {}; /// - /// @brief Apply display index (used from x11) + /// @brief Apply display index (used from qt) /// virtual void setDisplayIndex(int index) {}; diff --git a/include/hyperion/Hyperion.h b/include/hyperion/Hyperion.h index c79d6237..d9510067 100644 --- a/include/hyperion/Hyperion.h +++ b/include/hyperion/Hyperion.h @@ -360,8 +360,14 @@ public slots: /// @param args arguments of the effect script /// @param priority The priority channel of the effect /// @param timeout The timeout of the effect (after the timout, the effect will be cleared) - int setEffect(const QString & effectName, const QJsonObject & args, int priority, - int timeout = -1, const QString & pythonScript = "", const QString & origin="System"); + int setEffect(const QString &effectName + , const QJsonObject &args + , int priority + , int timeout = -1 + , const QString &pythonScript = "" + , const QString &origin="System" + , const QString &imageData = "" + ); /// sets the methode how image is maped to leds at ImageProcessor void setLedMappingType(const int& mappingType); diff --git a/include/hyperion/ImageToLedsMap.h b/include/hyperion/ImageToLedsMap.h index 7896592d..637bb050 100644 --- a/include/hyperion/ImageToLedsMap.h +++ b/include/hyperion/ImageToLedsMap.h @@ -168,7 +168,9 @@ namespace hyperion template ColorRgb calcMeanColor(const Image & image, const std::vector & colors) const { - if (colors.size() == 0) + const auto colorVecSize = colors.size(); + + if (colorVecSize == 0) { return ColorRgb::BLACK; } @@ -177,18 +179,20 @@ namespace hyperion uint_fast16_t cummRed = 0; uint_fast16_t cummGreen = 0; uint_fast16_t cummBlue = 0; + const auto& imgData = image.memptr(); + for (const unsigned colorOffset : colors) { - const Pixel_T& pixel = image.memptr()[colorOffset]; + const auto& pixel = imgData[colorOffset]; cummRed += pixel.red; cummGreen += pixel.green; cummBlue += pixel.blue; } // Compute the average of each color channel - const uint8_t avgRed = uint8_t(cummRed/colors.size()); - const uint8_t avgGreen = uint8_t(cummGreen/colors.size()); - const uint8_t avgBlue = uint8_t(cummBlue/colors.size()); + const uint8_t avgRed = uint8_t(cummRed/colorVecSize); + const uint8_t avgGreen = uint8_t(cummGreen/colorVecSize); + const uint8_t avgBlue = uint8_t(cummBlue/colorVecSize); // Return the computed color return {avgRed, avgGreen, avgBlue}; @@ -211,9 +215,11 @@ namespace hyperion uint_fast16_t cummBlue = 0; const unsigned imageSize = image.width() * image.height(); + const auto& imgData = image.memptr(); + for (unsigned idx=0; idx #include #include -// #include -// #include -// #include #include // hyperion includes @@ -180,7 +177,7 @@ void JsonAPI::handleImageCommand(const QJsonObject& message, const QString& comm sendSuccessReply(command, tan); } -void JsonAPI::handleEffectCommand(const QJsonObject& message, const QString& command, const int tan) +void JsonAPI::handleEffectCommand(const QJsonObject &message, const QString &command, const int tan) { emit forwardJsonMessage(message); @@ -191,16 +188,12 @@ void JsonAPI::handleEffectCommand(const QJsonObject& message, const QString& com QString origin = message["origin"].toString() + "@"+_peerAddress; const QJsonObject & effect = message["effect"].toObject(); const QString & effectName = effect["name"].toString(); + const QString & data = message["imageData"].toString("").toUtf8(); // set output - if (effect.contains("args")) - { - _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin); - } - else - { - _hyperion->setEffect(effectName, priority, duration, origin); - } + (effect.contains("args")) + ? _hyperion->setEffect(effectName, effect["args"].toObject(), priority, duration, pythonScript, origin, data) + : _hyperion->setEffect(effectName, priority, duration, origin); // send reply sendSuccessReply(command, tan); diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index 86e61675..8e49a76b 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -25,7 +25,7 @@ //impl PyThreadState* mainThreadState; -Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString & script, const QString & name, const QJsonObject & args) +Effect::Effect(Hyperion *hyperion, int priority, int timeout, const QString &script, const QString &name, const QJsonObject &args, const QString &imageData) : QThread() , _hyperion(hyperion) , _priority(priority) @@ -33,6 +33,7 @@ Effect::Effect(Hyperion* hyperion, int priority, int timeout, const QString & sc , _script(script) , _name(name) , _args(args) + , _imageData(imageData) , _endTime(-1) , _colors() , _imageSize(hyperion->getLedGridSize()) diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index 7c89f66d..058b166c 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -136,14 +136,14 @@ int EffectEngine::runEffect(const QString &effectName, int priority, int timeout return runEffect(effectName, QJsonObject(), priority, timeout, "", origin); } -int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString &pythonScript, const QString &origin, unsigned smoothCfg) +int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString &pythonScript, const QString &origin, unsigned smoothCfg, const QString &imageData) { Info( _log, "run effect %s on channel %d", QSTRING_CSTR(effectName), priority); if (pythonScript.isEmpty()) { - const EffectDefinition * effectDefinition = nullptr; - for (const EffectDefinition & e : _availableEffects) + const EffectDefinition *effectDefinition = nullptr; + for (const EffectDefinition &e : _availableEffects) { if (e.name == effectName) { @@ -160,16 +160,16 @@ int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, return runEffectScript(effectDefinition->script, effectName, (args.isEmpty() ? effectDefinition->args : args), priority, timeout, origin, effectDefinition->smoothCfg); } - return runEffectScript(pythonScript, effectName, args, priority, timeout, origin, smoothCfg); + return runEffectScript(pythonScript, effectName, args, priority, timeout, origin, smoothCfg, imageData); } -int EffectEngine::runEffectScript(const QString &script, const QString &name, const QJsonObject &args, int priority, int timeout, const QString & origin, unsigned smoothCfg) +int EffectEngine::runEffectScript(const QString &script, const QString &name, const QJsonObject &args, int priority, int timeout, const QString &origin, unsigned smoothCfg, const QString &imageData) { // clear current effect on the channel channelCleared(priority); // create the effect - Effect * effect = new Effect(_hyperion, priority, timeout, script, name, args); + Effect *effect = new Effect(_hyperion, priority, timeout, script, name, args, imageData); connect(effect, &Effect::setInput, _hyperion, &Hyperion::setInput, Qt::QueuedConnection); connect(effect, &Effect::setInputImage, _hyperion, &Hyperion::setInputImage, Qt::QueuedConnection); connect(effect, &QThread::finished, this, &EffectEngine::effectFinished); diff --git a/libsrc/effectengine/EffectFileHandler.cpp b/libsrc/effectengine/EffectFileHandler.cpp index 584369ec..aa4d8800 100644 --- a/libsrc/effectengine/EffectFileHandler.cpp +++ b/libsrc/effectengine/EffectFileHandler.cpp @@ -8,6 +8,7 @@ #include #include #include +#include // createEffect helper struct find_schema: std::unary_function @@ -69,7 +70,15 @@ const bool EffectFileHandler::deleteEffect(const QString& effectName, QString& r { if (effectConfigurationFile.exists()) { + if ( (it->script == ":/effects/gif.py") && !it->args.value("image").toString("").isEmpty()) + { + QFileInfo effectImageFile(effectConfigurationFile.absolutePath() + "/" + it->args.value("image").toString()); + if (effectImageFile.exists()) + QFile::remove(effectImageFile.absoluteFilePath()); + } + bool result = QFile::remove(effectConfigurationFile.absoluteFilePath()); + if (result) { updateEffects(); @@ -141,6 +150,17 @@ const bool EffectFileHandler::saveEffect(const QJsonObject& message, QString& re newFileName.setFile(f); } + //TODO check if filename exist + if (!message["imageData"].toString("").isEmpty() && !message["args"].toObject().value("image").toString("").isEmpty()) + { + QFileInfo imageFileName(effectArray[0].toString().replace("$ROOT",_rootPath) + "/" + message["args"].toObject().value("image").toString()); + if(!FileUtils::writeFile(imageFileName.absoluteFilePath(), QByteArray::fromBase64(message["imageData"].toString("").toUtf8()), _log)) + { + resultMsg = "Error while saving image file '" + message["args"].toObject().value("image").toString() + ", please check the Hyperion Log"; + return false; + } + } + if(!JsonUtils::write(newFileName.absoluteFilePath(), effectJson, _log)) { resultMsg = "Error while saving effect, please check the Hyperion Log"; diff --git a/libsrc/effectengine/EffectModule.cpp b/libsrc/effectengine/EffectModule.cpp index 528ab5d2..d227d3d6 100644 --- a/libsrc/effectengine/EffectModule.cpp +++ b/libsrc/effectengine/EffectModule.cpp @@ -11,6 +11,7 @@ #include #include #include +#include // create the hyperion module struct PyModuleDef EffectModule::moduleDef = { @@ -257,25 +258,42 @@ PyObject* EffectModule::wrapSetImage(PyObject *self, PyObject *args) PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args) { - Q_INIT_RESOURCE(EffectEngine); + Effect *effect = getEffect(); - char *source; - if(!PyArg_ParseTuple(args, "s", &source)) + QString file; + QBuffer buffer; + QImageReader reader; + + if (effect->_imageData.isEmpty()) { - PyErr_SetString(PyExc_TypeError, "String required"); - return NULL; + Q_INIT_RESOURCE(EffectEngine); + + char *source; + if(!PyArg_ParseTuple(args, "s", &source)) + { + PyErr_SetString(PyExc_TypeError, "String required"); + return NULL; + } + + file = QString::fromUtf8(source); + + if (file.mid(0, 1) == ":") + file = ":/effects/"+file.mid(1); + + reader.setDecideFormatFromContent(true); + reader.setFileName(file); + } + else + { + buffer.setData(QByteArray::fromBase64(effect->_imageData.toUtf8())); + buffer.open(QBuffer::ReadOnly); + reader.setDecideFormatFromContent(true); + reader.setDevice(&buffer); } - - QString file = QString::fromUtf8(source); - - if (file.mid(0, 1) == ":") - file = ":/effects/"+file.mid(1); - - QImageReader reader(file); if (reader.canRead()) { - PyObject* result = PyList_New(reader.imageCount()); + PyObject *result = PyList_New(reader.imageCount()); for (int i = 0; i < reader.imageCount(); ++i) { @@ -290,7 +308,7 @@ PyObject* EffectModule::wrapGetImage(PyObject *self, PyObject *args) QByteArray binaryImage; for (int i = 0; i(qimage.scanLine(i)); + const QRgb *scanline = reinterpret_cast(qimage.scanLine(i)); for (int j = 0; j< width; ++j) { binaryImage.append((char) qRed(scanline[j])); diff --git a/libsrc/grabber/CMakeLists.txt b/libsrc/grabber/CMakeLists.txt index a075e79d..6163f5b3 100644 --- a/libsrc/grabber/CMakeLists.txt +++ b/libsrc/grabber/CMakeLists.txt @@ -21,3 +21,7 @@ endif (ENABLE_V4L2) if (ENABLE_X11) add_subdirectory(x11) endif() + +if (ENABLE_QT) + add_subdirectory(qt) +endif() diff --git a/libsrc/grabber/qt/CMakeLists.txt b/libsrc/grabber/qt/CMakeLists.txt new file mode 100644 index 00000000..1ac244e3 --- /dev/null +++ b/libsrc/grabber/qt/CMakeLists.txt @@ -0,0 +1,16 @@ +# Define the current source locations +SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/grabber) +SET(CURRENT_SOURCE_DIR ${CMAKE_SOURCE_DIR}/libsrc/grabber/qt) + +find_package(Qt5Widgets REQUIRED) + +include_directories( ${X11_INCLUDES} ) + +FILE ( GLOB QT_GRAB_SOURCES "${CURRENT_HEADER_DIR}/Qt*.h" "${CURRENT_SOURCE_DIR}/*.h" "${CURRENT_SOURCE_DIR}/*.cpp" ) + +add_library(qt-grabber ${QT_GRAB_SOURCES} ) + +target_link_libraries(qt-grabber + hyperion + Qt5::Widgets +) diff --git a/libsrc/grabber/qt/QtGrabber.cpp b/libsrc/grabber/qt/QtGrabber.cpp new file mode 100644 index 00000000..7e5b7d83 --- /dev/null +++ b/libsrc/grabber/qt/QtGrabber.cpp @@ -0,0 +1,199 @@ +// proj +#include + +// qt +#include +#include +#include +#include +#include + +QtGrabber::QtGrabber(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display) + : Grabber("QTGRABBER", 0, 0, cropLeft, cropRight, cropTop, cropBottom) + , _display(unsigned(display)) + , _pixelDecimation(pixelDecimation) + , _screenWidth(0) + , _screenHeight(0) + , _src_x(0) + , _src_y(0) + , _src_x_max(0) + , _src_y_max(0) + , _screen(nullptr) +{ + _useImageResampler = false; + + // init + setupDisplay(); +} + +QtGrabber::~QtGrabber() +{ + freeResources(); +} + +void QtGrabber::freeResources() +{ + // cleanup + if (_screen != nullptr) + { + delete _screen; + _screen = nullptr; + } +} + +const bool QtGrabber::setupDisplay() +{ + // cleanup last screen + freeResources(); + + QScreen* primary = QGuiApplication::primaryScreen(); + QList screens = QGuiApplication::screens(); + // inject main screen at 0, if not nullptr + if(primary != nullptr) + { + screens.prepend(primary); + // remove last main screen if twice in list + if(screens.lastIndexOf(primary) > 0) + screens.removeAt(screens.lastIndexOf(primary)); + } + + if(screens.isEmpty()) + { + Error(_log, "No displays found to capture from!"); + return false; + } + + Info(_log,"Available Displays:"); + int index = 0; + for(auto screen : screens) + { + const QRect geo = screen->geometry(); + Info(_log,"Display %d: Name:%s Geometry: (L,T,R,B) %d,%d,%d,%d Depth:%dbit", index, QSTRING_CSTR(screen->name()), geo.left(), geo.top() ,geo.right(), geo.bottom(), screen->depth()); + index++; + } + + // be sure the index is available + if(_display > unsigned(screens.size()-1)) + { + Info(_log, "The requested display index '%d' is not available, falling back to display 0", _display); + _display = 0; + } + + // init the requested display + _screen = screens.at(_display); + connect(_screen, &QScreen::geometryChanged, this, &QtGrabber::geometryChanged); + updateScreenDimensions(true); + + Info(_log,"Initialized display %d", _display); + return true; +} + +void QtGrabber::geometryChanged(const QRect &geo) +{ + Info(_log, "The current display changed geometry to (L,T,R,B) %d,%d,%d,%d", geo.left(), geo.top() ,geo.right(), geo.bottom()); + updateScreenDimensions(true); +} + +int QtGrabber::grabFrame(Image & image) +{ + if(_screen == nullptr) + { + // reinit, this will disable capture on failure + setEnabled(setupDisplay()); + return -1; + } + QPixmap originalPixmap = _screen->grabWindow(0, _src_x, _src_y, _src_x_max, _src_y_max); + QPixmap resizedPixmap = originalPixmap.scaled(_width,_height); + QImage img = resizedPixmap.toImage().convertToFormat( QImage::Format_RGB888); + memcpy(image.memptr(), img.bits(),_width*_height*3); + + return 0; +} + +int QtGrabber::updateScreenDimensions(const bool& force) +{ + if(!_screen) + return -1; + + const QRect& geo = _screen->geometry(); + if (!force && _screenWidth == unsigned(geo.right()) && _screenHeight == unsigned(geo.bottom())) + { + // No update required + return 0; + } + + Info(_log, "Update of screen resolution: [%dx%d] to [%dx%d]", _screenWidth, _screenHeight, geo.right(), geo.bottom()); + _screenWidth = geo.right() - geo.left(); + _screenHeight = geo.bottom() - geo.top(); + + int width=0, height=0; + + // Image scaling is performed by Qt + width = (_screenWidth > unsigned(_cropLeft + _cropRight)) + ? ((_screenWidth - _cropLeft - _cropRight) / _pixelDecimation) + : (_screenWidth / _pixelDecimation); + + height = (_screenHeight > unsigned(_cropTop + _cropBottom)) + ? ((_screenHeight - _cropTop - _cropBottom) / _pixelDecimation) + : (_screenHeight / _pixelDecimation); + + + // calculate final image dimensions and adjust top/left cropping in 3D modes + switch (_videoMode) + { + case VIDEO_3DSBS: + _width = width /2; + _height = height; + _src_x = _cropLeft / 2; + _src_y = _cropTop; + _src_x_max = (_screenWidth / 2) - _cropRight; + _src_y_max = _screenHeight - _cropBottom; + break; + case VIDEO_3DTAB: + _width = width; + _height = height / 2; + _src_x = _cropLeft; + _src_y = _cropTop / 2; + _src_x_max = _screenWidth - _cropRight; + _src_y_max = (_screenHeight / 2) - _cropBottom; + break; + case VIDEO_2D: + default: + _width = width; + _height = height; + _src_x = _cropLeft; + _src_y = _cropTop; + _src_x_max = _screenWidth - _cropRight; + _src_y_max = _screenHeight - _cropBottom; + break; + } + + Info(_log, "Update output image resolution to [%dx%d]", _width, _height); + return 1; +} + +void QtGrabber::setVideoMode(VideoMode mode) +{ + Grabber::setVideoMode(mode); + updateScreenDimensions(true); +} + +void QtGrabber::setPixelDecimation(int pixelDecimation) +{ + _pixelDecimation = pixelDecimation; +} + +void QtGrabber::setCropping(unsigned cropLeft, unsigned cropRight, unsigned cropTop, unsigned cropBottom) +{ + Grabber::setCropping(cropLeft, cropRight, cropTop, cropBottom); + updateScreenDimensions(true); +} + +void QtGrabber::setDisplayIndex(int index) +{ + if(_display != unsigned(index)) + { + _display = unsigned(index); + setupDisplay(); + } +} diff --git a/libsrc/grabber/qt/QtWrapper.cpp b/libsrc/grabber/qt/QtWrapper.cpp new file mode 100644 index 00000000..6326711c --- /dev/null +++ b/libsrc/grabber/qt/QtWrapper.cpp @@ -0,0 +1,11 @@ +#include + +QtWrapper::QtWrapper(int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display, const unsigned updateRate_Hz) + : GrabberWrapper("Qt", &_grabber, 0, 0, updateRate_Hz) + , _grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation, display) +{} + +void QtWrapper::action() +{ + transferFrame(_grabber); +} diff --git a/libsrc/hyperion/GrabberWrapper.cpp b/libsrc/hyperion/GrabberWrapper.cpp index 0f1bc959..41827b04 100644 --- a/libsrc/hyperion/GrabberWrapper.cpp +++ b/libsrc/hyperion/GrabberWrapper.cpp @@ -77,6 +77,10 @@ QStringList GrabberWrapper::availableGrabbers() grabbers << "x11"; #endif + #ifdef ENABLE_QT + grabbers << "qt"; + #endif + return grabbers; } diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 0fb7b1d8..b45b67ea 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -529,9 +529,9 @@ int Hyperion::setEffect(const QString &effectName, int priority, int timeout, co return _effectEngine->runEffect(effectName, priority, timeout, origin); } -int Hyperion::setEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString & pythonScript, const QString & origin) +int Hyperion::setEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString &pythonScript, const QString &origin, const QString &imageData) { - return _effectEngine->runEffect(effectName, args, priority, timeout, pythonScript, origin); + return _effectEngine->runEffect(effectName, args, priority, timeout, pythonScript, origin, 0, imageData); } void Hyperion::setLedMappingType(const int& mappingType) diff --git a/libsrc/hyperion/ImageToLedsMap.cpp b/libsrc/hyperion/ImageToLedsMap.cpp index d02e9c81..7f3c7d5e 100644 --- a/libsrc/hyperion/ImageToLedsMap.cpp +++ b/libsrc/hyperion/ImageToLedsMap.cpp @@ -47,19 +47,24 @@ ImageToLedsMap::ImageToLedsMap( minX_idx = qMin(minX_idx, xOffset + actualWidth - 1); if (minX_idx == maxX_idx) { - maxX_idx = minX_idx + 1; + maxX_idx++; } minY_idx = qMin(minY_idx, yOffset + actualHeight - 1); if (minY_idx == maxY_idx) { - maxY_idx = minY_idx + 1; + maxY_idx++; } // Add all the indices in the above defined rectangle to the indices for this led + const auto maxYLedCount = qMin(maxY_idx, yOffset+actualHeight); + const auto maxXLedCount = qMin(maxX_idx, xOffset+actualWidth); + std::vector ledColors; - for (unsigned y = minY_idx; y & QtWrapper::getScreenshot() +{ + _grabber.grabFrame(_screenshot); + return _screenshot; +} + +void QtWrapper::start() +{ + _timer.start(); +} + +void QtWrapper::stop() +{ + _timer.stop(); +} + +void QtWrapper::capture() +{ + if(unsigned(_grabber.getImageWidth()) != unsigned(_screenshot.width()) || unsigned(_grabber.getImageHeight()) != unsigned(_screenshot.height())) + _screenshot.resize(_grabber.getImageWidth(),_grabber.getImageHeight()); + + _grabber.grabFrame(_screenshot); + emit sig_screenshot(_screenshot); +} + +void QtWrapper::setVideoMode(const VideoMode mode) +{ + _grabber.setVideoMode(mode); +} diff --git a/src/hyperion-qt/QtWrapper.h b/src/hyperion-qt/QtWrapper.h new file mode 100644 index 00000000..327795ec --- /dev/null +++ b/src/hyperion-qt/QtWrapper.h @@ -0,0 +1,51 @@ + +// QT includes +#include + +// Hyperion-Qt includes +#include + +//Utils includes +#include + +class QtWrapper : public QObject +{ + Q_OBJECT +public: + QtWrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display); + + const Image & getScreenshot(); + + /// + /// Starts the timed capturing of screenshots + /// + void start(); + + void stop(); + +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 + QtGrabber _grabber; + + Image _screenshot; +}; diff --git a/src/hyperion-qt/hyperion-qt.cpp b/src/hyperion-qt/hyperion-qt.cpp new file mode 100644 index 00000000..00fe7614 --- /dev/null +++ b/src/hyperion-qt/hyperion-qt.cpp @@ -0,0 +1,111 @@ + +// QT includes +#include +#include + +#include "QtWrapper.h" +#include +#include +#include + +//flatbuf sending +#include + +// 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) +{ + //QCoreApplication app(argc, argv); + QGuiApplication app(argc, argv); + + try + { + // create the option parser and initialize all parameters + Parser parser("Qt interface capture application for Hyperion"); + + Option & argDisplay = parser.add