Commits from @MartB and more ...

- Commit: 1d9165f403
- New default QT capture implementation
- UploadHandler added to Effects Configurator to allow uploading GIF files
- Docker compile script and instruction
- Travis Fix
This commit is contained in:
Paulchen-Panther 2019-01-06 19:49:56 +01:00
parent 7352ff4d42
commit 2dca1c93e6
57 changed files with 1134 additions and 341 deletions

2
.gitmodules vendored
View File

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

View File

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

View File

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

View File

@ -1,10 +1,13 @@
#!/bin/bash
# sf_upload <deploylist> <sf_dir>
# sf_upload <FILES> <sf_dir>
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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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!",

View File

@ -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!",

View File

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

View File

@ -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; idx<effects.length; idx++)
{
$("#effectslist").append(createSelOpt(effects[idx].schemaContent.script, $.i18n(effects[idx].schemaContent.title)));
}
{
$("#effectslist").append(createSelOpt(effects[idx].schemaContent.script, $.i18n(effects[idx].schemaContent.title)));
}
$("#effectslist").trigger("change");
updateDelEffectlist();

View File

@ -41,7 +41,7 @@ $(document).ready( function() {
currentVersion = sysInfo.hyperion.version;
});
$(hyperion).on("cmd-config-getschema", function(event) {
$(hyperion).one("cmd-config-getschema", function(event) {
serverSchema = event.response.info;
requestServerConfig();
@ -63,7 +63,7 @@ $(document).ready( function() {
requestServerConfigSchema();
});
$(hyperion).on("ready", function(event) {
$(hyperion).one("ready", function(event) {
loadContent();
});

View File

@ -270,15 +270,15 @@ function requestWriteConfig(config, full)
sendToHyperion("config","setconfig", '"config":'+JSON.stringify(serverConfig));
}
function requestWriteEffect(effectName,effectPy,effectArgs)
function requestWriteEffect(effectName,effectPy,effectArgs,data)
{
var cutArgs = effectArgs.slice(1, -1);
sendToHyperion("create-effect", "", '"name":"'+effectName+'", "script":"'+effectPy+'", '+cutArgs);
sendToHyperion("create-effect", "", '"name":"'+effectName+'", "script":"'+effectPy+'", '+cutArgs+',"imageData":"'+data+'"');
}
function requestTestEffect(effectName,effectPy,effectArgs)
function requestTestEffect(effectName,effectPy,effectArgs,data)
{
sendToHyperion("effect", "", '"effect":{"name":"'+effectName+'", "args":'+effectArgs+'}, "priority":'+webPrio+', "origin":"'+webOrigin+'", "pythonScript":"'+effectPy+'"');
sendToHyperion("effect", "", '"effect":{"name":"'+effectName+'", "args":'+effectArgs+'}, "priority":'+webPrio+', "origin":"'+webOrigin+'", "pythonScript":"'+effectPy+'", "imageData":"'+data+'"');
}
function requestDeleteEffect(effectName)

View File

@ -10,7 +10,7 @@ $(document).ready(function() {
var canvas_height;
var canvas_width;
var twoDPaths = [];
var toggleLeds = false;
var toggleLeds, toggleLedsNum = false;
/// add prototype for simple canvas clear() method
CanvasRenderingContext2D.prototype.clear = function(){
@ -148,9 +148,17 @@ $(document).ready(function() {
// can be used as fallback when Path2D is not available
//roundRect(ledsCanvasNodeCtx, led.hscan.minimum * canvas_width, led.vscan.minimum * canvas_height, (led.hscan.maximum-led.hscan.minimum) * canvas_width, (led.vscan.maximum-led.vscan.minimum) * canvas_height, 4, true, colors[idx])
//ledsCanvasNodeCtx.fillRect(led.hscan.minimum * canvas_width, led.vscan.minimum * canvas_height, (led.hscan.maximum-led.hscan.minimum) * canvas_width, (led.vscan.maximum-led.vscan.minimum) * canvas_height);
ledsCanvasNodeCtx.fillStyle = (useColor) ? "rgba("+colors[idx].red+","+colors[idx].green+","+colors[idx].blue+",0.9)" : "hsl("+(idx*360/leds.length)+",100%,50%)";
ledsCanvasNodeCtx.fill(twoDPaths[idx]);
ledsCanvasNodeCtx.stroke(twoDPaths[idx]);
if(toggleLedsNum)
{
ledsCanvasNodeCtx.fillStyle = "blue";
ledsCanvasNodeCtx.textAlign = "center";
ledsCanvasNodeCtx.fillText(idx, (led.hscan.minimum * canvas_width) + ( ((led.hscan.maximum-led.hscan.minimum) * canvas_width) / 2), (led.vscan.minimum * canvas_height) + ( ((led.vscan.maximum-led.vscan.minimum) * canvas_height) / 2));
}
}
}
@ -173,12 +181,11 @@ $(document).ready(function() {
resetImage()
}
// -----------------------FIX THIS-------------------------
// ------------------------------------------------------------------
// $('#leds_toggle_num').off().on("click", function() {
// $('.led_num').toggle();
// toggleClass('#leds_toggle_num', "btn-danger", "btn-success");
//});
$('#leds_toggle_num').off().on("click", function() {
toggleLedsNum = !toggleLedsNum
toggleClass('#leds_toggle_num', "btn-danger", "btn-success");
});
// ------------------------------------------------------------------
$('#leds_toggle').off().on("click", function() {

View File

@ -5982,24 +5982,8 @@ JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({
if(!this.preview_value) return;
var self = this;
var mime = this.preview_value.match(/^data:([^;,]+)[;,]/);
if(mime) mime = mime[1];
if(!mime) mime = 'unknown';
var file = this.uploader.files[0];
this.preview.innerHTML = '<strong>Type:</strong> '+mime+', <strong>Size:</strong> '+file.size+' bytes';
if(mime.substr(0,5)==="image") {
this.preview.innerHTML += '<br>';
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 += '<br>';
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() {

View File

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

View File

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

View File

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

View File

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

View File

@ -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"
},

View File

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

View File

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

View File

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

View File

@ -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<ColorRgb>& ledColors, const int timeout_ms, const bool& clearEffect);
void setInputImage(const int priority, const Image<ColorRgb>& image, const int timeout_ms, const bool& clearEffect);
void setInput(const int priority, const std::vector<ColorRgb> &ledColors, const int timeout_ms, const bool &clearEffect);
void setInputImage(const int priority, const Image<ColorRgb> &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<ColorRgb> _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<QImage> _imageStack;
};

View File

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

View File

@ -0,0 +1,96 @@
#pragma once
#include <QObject>
// Hyperion-utils includes
#include <utils/ColorRgb.h>
#include <hyperion/Grabber.h>
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<ColorRgb> & 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;
};

View File

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

View File

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

View File

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

View File

@ -168,7 +168,9 @@ namespace hyperion
template <typename Pixel_T>
ColorRgb calcMeanColor(const Image<Pixel_T> & image, const std::vector<unsigned> & 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<imageSize; idx++)
{
const Pixel_T& pixel = image.memptr()[idx];
const auto& pixel = imgData[idx];
cummRed += pixel.red;
cummGreen += pixel.green;
cummBlue += pixel.blue;

View File

@ -24,6 +24,10 @@
{
"type" : "object",
"required" : true
},
"imageData" : {
"type" : "string",
"required" : false
}
},
"additionalProperties": false

View File

@ -44,6 +44,10 @@
"pythonScript" : {
"type" : "string",
"required" : false
},
"imageData" : {
"type" : "string",
"required" : false
}
},
"additionalProperties": false

View File

@ -12,9 +12,6 @@
#include <QImage>
#include <QBuffer>
#include <QByteArray>
// #include <QFileInfo>
// #include <QDir>
// #include <QIODevice>
#include <QDateTime>
// 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);

View File

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

View File

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

View File

@ -8,6 +8,7 @@
#include <QFileInfo>
#include <QDir>
#include <QMap>
#include <QByteArray>
// createEffect helper
struct find_schema: std::unary_function<EffectSchema, bool>
@ -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";

View File

@ -11,6 +11,7 @@
#include <QJsonArray>
#include <QDateTime>
#include <QImageReader>
#include <QBuffer>
// 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<height; ++i)
{
const QRgb * scanline = reinterpret_cast<const QRgb *>(qimage.scanLine(i));
const QRgb *scanline = reinterpret_cast<const QRgb *>(qimage.scanLine(i));
for (int j = 0; j< width; ++j)
{
binaryImage.append((char) qRed(scanline[j]));

View File

@ -21,3 +21,7 @@ endif (ENABLE_V4L2)
if (ENABLE_X11)
add_subdirectory(x11)
endif()
if (ENABLE_QT)
add_subdirectory(qt)
endif()

View File

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

View File

@ -0,0 +1,199 @@
// proj
#include <grabber/QtGrabber.h>
// qt
#include <QPixmap>
#include <QWindow>
#include <QGuiApplication>
#include <QWidget>
#include <QScreen>
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<QScreen *> 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<ColorRgb> & 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();
}
}

View File

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

View File

@ -77,6 +77,10 @@ QStringList GrabberWrapper::availableGrabbers()
grabbers << "x11";
#endif
#ifdef ENABLE_QT
grabbers << "qt";
#endif
return grabbers;
}

View File

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

View File

@ -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<unsigned> ledColors;
for (unsigned y = minY_idx; y<maxY_idx && y<(yOffset+actualHeight); ++y)
ledColors.reserve(maxXLedCount*maxYLedCount);
for (unsigned y = minY_idx; y < maxYLedCount; ++y)
{
for (unsigned x = minX_idx; x<maxX_idx && x<(xOffset+actualWidth); ++x)
for (unsigned x = minX_idx; x < maxXLedCount; ++x)
{
ledColors.push_back(y*width + x);
}

View File

@ -7,7 +7,7 @@
{
"type" : "string",
"title" : "edt_conf_fg_type_title",
"enum" : ["auto","dispmanx","amlogic","x11","framebuffer"],
"enum" : ["auto","dispmanx","amlogic","x11","framebuffer","qt"],
"default" : "auto",
"propertyOrder" : 2
},

View File

@ -144,8 +144,8 @@ void RgbTransform::transform(uint8_t & red, uint8_t & green, uint8_t & blue)
{
// apply gamma
red = _mappingR[red];
green = _mappingR[green];
blue = _mappingR[blue];
green = _mappingG[green];
blue = _mappingB[blue];
// apply brightnesss
int rgbSum = red+green+blue;

View File

@ -23,6 +23,8 @@ QtHttpClientWrapper::QtHttpClientWrapper (QTcpSocket * sock, QtHttpServer * pare
, m_sockClient (sock)
, m_currentRequest (Q_NULLPTR)
, m_serverHandle (parent)
, m_websocketClient(nullptr)
, m_webJsonRpc (nullptr)
{
connect (m_sockClient, &QTcpSocket::readyRead, this, &QtHttpClientWrapper::onClientDataReceived);
}

View File

@ -50,8 +50,8 @@ private:
QTcpSocket * m_sockClient;
QtHttpRequest * m_currentRequest;
QtHttpServer * m_serverHandle;
WebSocketClient * m_websocketClient = nullptr;
WebJsonRpc * m_webJsonRpc = nullptr;
WebSocketClient * m_websocketClient;
WebJsonRpc * m_webJsonRpc;
};
#endif // QTHTTPCLIENTWRAPPER_H

View File

@ -0,0 +1,42 @@
cmake_minimum_required(VERSION 3.0.0)
project(hyperion-qt)
find_package(Qt5Widgets REQUIRED)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}/../../libsrc/flatbufserver
${FLATBUFFERS_INCLUDE_DIRS}
)
set(Hyperion_QT_HEADERS
QtWrapper.h
)
set(Hyperion_QT_SOURCES
QtWrapper.cpp
hyperion-qt.cpp
)
add_executable(${PROJECT_NAME}
${Hyperion_QT_HEADERS}
${Hyperion_QT_SOURCES}
)
target_link_libraries(${PROJECT_NAME}
commandline
qt-grabber
flatbufserver
flatbuffers
ssdp
Qt5::Core
Qt5::Widgets
Qt5::Network
)
install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" )
if(CMAKE_HOST_UNIX)
install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "${PLATFORM}" )
install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "${PLATFORM}" )
install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "${PLATFORM}" )
endif(CMAKE_HOST_UNIX)

View File

@ -0,0 +1,42 @@
// Hyperion-qt includes
#include "QtWrapper.h"
QtWrapper::QtWrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display) :
_timer(this),
_grabber(cropLeft, cropRight, cropTop, cropBottom, pixelDecimation, display)
{
_timer.setInterval(grabInterval);
// Connect capturing to the timeout signal of the timer
connect(&_timer, SIGNAL(timeout()), this, SLOT(capture()));
}
const Image<ColorRgb> & 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);
}

View File

@ -0,0 +1,51 @@
// QT includes
#include <QTimer>
// Hyperion-Qt includes
#include <grabber/QtGrabber.h>
//Utils includes
#include <utils/VideoMode.h>
class QtWrapper : public QObject
{
Q_OBJECT
public:
QtWrapper(int grabInterval, int cropLeft, int cropRight, int cropTop, int cropBottom, int pixelDecimation, int display);
const Image<ColorRgb> & getScreenshot();
///
/// Starts the timed capturing of screenshots
///
void start();
void stop();
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
QtGrabber _grabber;
Image<ColorRgb> _screenshot;
};

View File

@ -0,0 +1,111 @@
// QT includes
#include <QCoreApplication>
#include <QImage>
#include "QtWrapper.h"
#include <utils/ColorRgb.h>
#include <utils/Image.h>
#include <commandline/Parser.h>
//flatbuf sending
#include <flatbufserver/FlatBufferConnection.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)
{
//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<Option> ('d', "display", "Set the display to capture [default: %1]","0");
IntOption & argFps = parser.add<IntOption> ('f', "framerate", "Capture frame rate [default: %1]", "10", 1, 25);
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 image 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:19445");
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 arguments
parser.process(app);
// check if we need to display the usage. exit if we do.
if (parser.isSet(argHelp))
{
parser.showHelp(0);
}
QtWrapper grabber(
1000 / argFps.getInt(parser),
argCropLeft.getInt(parser),
argCropRight.getInt(parser),
argCropTop.getInt(parser),
argCropBottom.getInt(parser),
argSizeDecimation.getInt(parser),
parser.isSet(argDisplay));
if (parser.isSet(argScreenshot))
{
// Capture a single screenshot and finish
const Image<ColorRgb> &screenshot = grabber.getScreenshot();
saveScreenshot("screenshot.png", screenshot);
}
else
{
// server searching by ssdp
QString address;
if(parser.isSet(argAddress))
{
address = argAddress.value(parser);
}
else
{
SSDPDiscover discover;
address = discover.getFirstService(STY_FLATBUFSERVER);
if(address.isEmpty())
{
address = argAddress.value(parser);
}
}
// Create the Flabuf-connection
FlatBufferConnection flatbuf("Qt Standalone", address, argPriority.getInt(parser), parser.isSet(argSkipReply));
// Connect the screen capturing to flatbuf connection processing
QObject::connect(&grabber, SIGNAL(sig_screenshot(const Image<ColorRgb> &)), &flatbuf, SLOT(setImage(Image<ColorRgb>)));
// Start the capturing
grabber.start();
// Start the application
app.exec();
}
}
catch (const std::runtime_error & e)
{
// An error occured. Display error and quit
Error(Logger::getInstance("QTGRABBER"), "%s", e.what());
return -1;
}
return 0;
}

View File

@ -1,4 +1,4 @@
find_package(PythonLibs 3.5 REQUIRED)
find_package(PythonLibs 3.4 REQUIRED)
include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..)
add_executable(hyperiond
@ -55,7 +55,9 @@ if (ENABLE_X11)
target_link_libraries(hyperiond x11-grabber )
endif ()
qt5_use_modules(hyperiond Core Gui Network Widgets)
if (ENABLE_QT)
target_link_libraries(hyperiond qt-grabber )
endif ()
install ( TARGETS hyperiond DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" )
install ( DIRECTORY ${CMAKE_SOURCE_DIR}/bin/service DESTINATION "share/hyperion/" COMPONENT "${PLATFORM}" )

View File

@ -60,6 +60,7 @@ HyperionDaemon::HyperionDaemon(QString configFile, const QString rootPath, QObje
, _amlGrabber(nullptr)
, _fbGrabber(nullptr)
, _osxGrabber(nullptr)
, _qtGrabber(nullptr)
, _hyperion(nullptr)
, _stats(nullptr)
, _ssdp(nullptr)
@ -155,7 +156,8 @@ void HyperionDaemon::freeObjects()
delete _dispmanx;
delete _fbGrabber;
delete _osxGrabber;
delete _qtGrabber;
for(V4L2Wrapper* grabber : _v4l2Grabbers)
{
delete grabber;
@ -168,6 +170,7 @@ void HyperionDaemon::freeObjects()
_dispmanx = nullptr;
_fbGrabber = nullptr;
_osxGrabber = nullptr;
_qtGrabber = nullptr;
_webserver = nullptr;
_jsonServer = nullptr;
_udpListener = nullptr;
@ -269,10 +272,10 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& type, const QJso
{
type = "x11";
}
// framebuffer -> if nothing other applies
// qt -> if nothing other applies
else
{
type = "framebuffer";
type = "qt";
}
}
@ -296,6 +299,9 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& type, const QJso
#ifdef ENABLE_X11
if(_x11Grabber != nullptr) _x11Grabber->stop();
#endif
#ifdef ENABLE_QT
if(_qtGrabber != nullptr) _qtGrabber->stop();
#endif
// create/start capture interface
if(type == "framebuffer")
@ -338,6 +344,19 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& type, const QJso
_x11Grabber->start();
#endif
}
else if(type == "qt")
{
if(_qtGrabber == nullptr)
createGrabberQt(grabberConfig);
#ifdef ENABLE_QT
_qtGrabber->start();
#endif
}
else
{
Error(_log,"Unknown platform capture type: %s", QSTRING_CSTR(type));
return;
}
_prevType = type;
}
}
@ -446,6 +465,24 @@ void HyperionDaemon::createGrabberX11(const QJsonObject & grabberConfig)
#endif
}
void HyperionDaemon::createGrabberQt(const QJsonObject & grabberConfig)
{
#ifdef ENABLE_QT
_qtGrabber = new QtWrapper(
_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom,
grabberConfig["pixelDecimation"].toInt(8),
grabberConfig["display"].toInt(0),
_grabber_frequency );
// connect to HyperionDaemon signal
connect(this, &HyperionDaemon::videoMode, _qtGrabber, &QtWrapper::setVideoMode);
connect(this, &HyperionDaemon::settingsChanged, _qtGrabber, &QtWrapper::handleSettingsUpdate);
Info(_log, "Qt grabber created");
#else
Error(_log, "The Qt grabber can not be instantiated, because it has been left out from the build");
#endif
}
void HyperionDaemon::createGrabberFramebuffer(const QJsonObject & grabberConfig)
{

View File

@ -39,6 +39,12 @@
typedef QObject X11Wrapper;
#endif
#ifdef ENABLE_QT
#include <grabber/QtWrapper.h>
#else
typedef QObject QtWrapper;
#endif
#include <utils/Logger.h>
#include <utils/Image.h>
#include <utils/VideoMode.h>
@ -122,6 +128,7 @@ private:
void createGrabberFramebuffer(const QJsonObject & grabberConfig);
void createGrabberOsx(const QJsonObject & grabberConfig);
void createGrabberX11(const QJsonObject & grabberConfig);
void createGrabberQt(const QJsonObject & grabberConfig);
Logger* _log;
BonjourBrowserWrapper* _bonjourBrowserWrapper;
@ -135,6 +142,7 @@ private:
AmlogicWrapper* _amlGrabber;
FramebufferWrapper* _fbGrabber;
OsxWrapper* _osxGrabber;
QtWrapper* _qtGrabber;
Hyperion* _hyperion;
Stats* _stats;
SSDPHandler* _ssdp;