From 510bb903ae7945c1493dbe269b09ad191aec3a9d Mon Sep 17 00:00:00 2001 From: brindosch Date: Tue, 12 May 2020 19:51:19 +0200 Subject: [PATCH] Windows compilation support (#738) * Disable AVAHI * Replace SysInfo backport with Qt SysInfo * Update vscode config * Update LedDevices * Update Logger * Update hyperiond * Update hyperion-remote * Exclude avahi * Empty definition for Process * PythonInit path broken * Exclude PiBlaster and link ws2_32 * more avahi * resolve ui bug * Update Compile howto * JsonAPI QtGrabber missing * fix error * ssize_t replacement * Nope, doesn't work * Adjust compile description and verify winSDK * Update ci script * Update ci script * Update ci * Update ci script * update Logger * Update PythonInit * added Azure & GitHub Actions, Logger, PythonInit * resolve merge conflicts * revert ssize_t in FadeCandy * look at registry for QT5 & use find_package(Python) if cmake >= 3.12 * second try * another try * and yet another test * qt5 registry search undone * Package creation test * finished package creation. only fine tuning is required :-) Signed-off-by: Paulchen-Panther * Dependencies for Windows finished Signed-off-by: Paulchen-Panther * use 'add_definitions()' until CMake 3.12 Signed-off-by: Paulchen-Panther * Update .github/workflows/pull-request.yml Co-Authored-By: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com> * Update cmake/Dependencies.cmake Co-Authored-By: brindosch * fix typo/ add VCINSTALLDIR var * fix again * Undo change again (Not working) * fix QT grabber Signed-off-by: Paulchen-Panther * first NSIS test Signed-off-by: Paulchen-Panther * Update NSIS package * surprise :-) Signed-off-by: Paulchen-Panther * Update NSIS package * fix: NSIS .bmps * Add nsis templates * Force windows gui app * fix: QSysInfo required Qt5.6, now it's 5.4 again * Update: Remove platform component and adjust package name * Add macOS as system name * Update docs * fix: Allow gh actions ci also for forks with branches * Add ReadMe docs, mention windows, add vscode linux debug config * fix: readme visual * reduce/hide banner/copyright/log message Infos here: https://docs.microsoft.com/de-de/visualstudio/msbuild/msbuild-command-line-reference?view=vs-2019#switches * Fix PythonInit * vscode: Add runner task * fix(vscode): compiler path gcc ver independent * fix azure * vscode: add windows run tasks * move process detection * main: add windows process detection * Azure file shredder * Update docs Co-authored-by: Paulchen Panther <16664240+Paulchen-Panther@users.noreply.github.com> Co-authored-by: Paulchen-Panther --- .azure.yml | 33 +- .ci/ci_build.sh | 11 + .ci/ci_install.sh | 5 + .github/workflows/pull-request.yml | 62 ++ .github/workflows/push-master.yml | 36 + .vscode/c_cpp_properties.json | 20 +- .vscode/hyperion.code-workspace | 28 + .vscode/launch.json | 38 + .vscode/tasks.json | 123 +++ CMakeLists.txt | 103 +- CONTRIBUTING.md | 19 +- CompileHowto.md | 22 + HyperionConfig.h.in | 3 + README.md | 21 +- assets/webconfig/js/ui_utils.js | 2 +- cmake/Dependencies.cmake | 286 +++++ cmake/FindWindowsSDK.cmake | 635 ++++++++++++ cmake/nsis/hyperion-logo-vert.bmp | Bin 0 -> 154542 bytes cmake/nsis/hyperion-logo.bmp | Bin 0 -> 88054 bytes cmake/nsis/icon.rc | 1 + cmake/nsis/installer.ico | Bin 0 -> 89173 bytes .../nsis/template/NSIS.InstallOptions.ini.in | 46 + cmake/nsis/template/NSIS.template.in | 978 ++++++++++++++++++ cmake/packages.cmake | 187 +++- dependencies/CMakeLists.txt | 8 +- docs/docs/en/user/Installation.md | 24 +- include/api/JsonCB.h | 8 +- include/utils/Image.h | 5 + include/utils/Logger.h | 13 +- include/utils/SysInfo.h | 24 - libsrc/CMakeLists.txt | 5 +- libsrc/api/JsonAPI.cpp | 11 +- libsrc/api/JsonCB.cpp | 12 +- libsrc/effectengine/CMakeLists.txt | 20 +- libsrc/grabber/qt/QtGrabber.cpp | 13 +- libsrc/hyperion/CMakeLists.txt | 5 +- libsrc/hyperion/Hyperion.cpp | 1 - libsrc/hyperion/LedString.cpp | 1 - libsrc/jsonserver/JsonServer.cpp | 5 +- libsrc/leddevice/CMakeLists.txt | 8 +- .../leddevice/dev_net/LedDeviceFadeCandy.cpp | 6 + .../leddevice/dev_net/LedDeviceUdpArtNet.cpp | 5 + libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h | 4 +- libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp | 5 + libsrc/leddevice/dev_net/LedDeviceUdpE131.h | 4 +- libsrc/leddevice/dev_net/ProviderUdp.cpp | 1 - libsrc/leddevice/dev_serial/LedDeviceDMX.cpp | 7 + libsrc/python/CMakeLists.txt | 19 +- libsrc/python/PythonInit.cpp | 18 +- libsrc/utils/Logger.cpp | 33 +- libsrc/utils/Process.cpp | 18 + libsrc/utils/RgbChannelAdjustment.cpp | 6 +- libsrc/utils/SysInfo.cpp | 282 +---- libsrc/webserver/WebServer.cpp | 6 +- src/CMakeLists.txt | 4 + src/hyperion-aml/CMakeLists.txt | 8 +- src/hyperion-dispmanx/CMakeLists.txt | 8 +- src/hyperion-framebuffer/CMakeLists.txt | 8 +- src/hyperion-osx/CMakeLists.txt | 8 +- src/hyperion-qt/CMakeLists.txt | 13 +- src/hyperion-remote/CMakeLists.txt | 18 +- src/hyperion-remote/hyperion-remote.cpp | 3 +- src/hyperion-v4l2/CMakeLists.txt | 8 +- src/hyperion-x11/CMakeLists.txt | 8 +- src/hyperiond/CMakeLists.txt | 235 +---- src/hyperiond/detectProcess.h | 84 ++ src/hyperiond/hyperiond.cpp | 293 +++--- src/hyperiond/main.cpp | 112 +- src/hyperiond/systray.cpp | 9 +- 69 files changed, 3184 insertions(+), 871 deletions(-) create mode 100644 .vscode/hyperion.code-workspace create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 cmake/Dependencies.cmake create mode 100644 cmake/FindWindowsSDK.cmake create mode 100644 cmake/nsis/hyperion-logo-vert.bmp create mode 100644 cmake/nsis/hyperion-logo.bmp create mode 100644 cmake/nsis/icon.rc create mode 100644 cmake/nsis/installer.ico create mode 100644 cmake/nsis/template/NSIS.InstallOptions.ini.in create mode 100644 cmake/nsis/template/NSIS.template.in create mode 100644 src/hyperiond/detectProcess.h diff --git a/.azure.yml b/.azure.yml index 165a19e7..837563e3 100644 --- a/.azure.yml +++ b/.azure.yml @@ -61,4 +61,35 @@ jobs: env: PLATFORM: 'osx' condition: succeeded() - displayName: 'Build macOS 10.13 packages' \ No newline at end of file + displayName: 'Build macOS 10.13 packages' + +###################### +###### windows ####### +###################### + +- job: windows + timeoutInMinutes: 120 + pool: + vmImage: 'windows-latest' + + steps: + - checkout: self # represents the repo where the initial Pipelines YAML file was found + submodules: recursive # set to 'recursive' to get submodules of submodules + + - task: UsePythonVersion@0 + inputs: + versionSpec: '3.x' + addToPath: true + architecture: 'x64' + + - script: | + cd $(Build.SourcesDirectory) + python -m pip install aqtinstall + python -m aqt install -O c:\Qt 5.14.0 windows desktop win64_msvc2017_64 + displayName: 'Install Qt 5.14.0' + # build process + - bash: ./.ci/ci_build.sh + env: + PLATFORM: 'windows' + condition: succeeded() + displayName: 'Build windows packages' diff --git a/.ci/ci_build.sh b/.ci/ci_build.sh index f1e1a1fe..740a4126 100755 --- a/.ci/ci_build.sh +++ b/.ci/ci_build.sh @@ -3,10 +3,12 @@ # detect CI if [ "$SYSTEM_COLLECTIONID" != "" ]; then # Azure Pipelines + echo "Azure detected" CI_NAME="$(echo "$AGENT_OS" | tr '[:upper:]' '[:lower:]')" CI_BUILD_DIR="$BUILD_SOURCESDIRECTORY" elif [ "$HOME" != "" ]; then # GitHub Actions + echo "Github Actions detected" CI_NAME="$(uname -s | tr '[:upper:]' '[:lower:]')" CI_BUILD_DIR="$GITHUB_WORKSPACE" else @@ -34,6 +36,15 @@ if [[ "$CI_NAME" == 'osx' || "$CI_NAME" == 'darwin' ]]; then cd ${CI_BUILD_DIR} && source /${CI_BUILD_DIR}/test/testrunner.sh || exit 4 exit 0; exit 1 || { echo "---> Hyperion compilation failed! Abort"; exit 5; } +elif [[ $CI_NAME == *"mingw64_nt"* || "$CI_NAME" == 'windows_nt' ]]; then + # compile prepare + echo "Number of Cores $NUMBER_OF_PROCESSORS" + mkdir build || exit 1 + cd build + cmake -G "Visual Studio 16 2019" -A x64 -DPLATFORM=${PLATFORM} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} ../ || exit 2 + cmake --build . --target package --config Release -- -nologo -v:m -maxcpucount || exit 3 + exit 0; + exit 1 || { echo "---> Hyperion compilation failed! Abort"; exit 5; } elif [[ "$CI_NAME" == 'linux' ]]; then echo "Compile Hyperion with DOCKER_TAG = ${DOCKER_TAG} and friendly name DOCKER_NAME = ${DOCKER_NAME}" # take ownership of deploy dir diff --git a/.ci/ci_install.sh b/.ci/ci_install.sh index ffb7ae34..0e4a19d2 100755 --- a/.ci/ci_install.sh +++ b/.ci/ci_install.sh @@ -38,6 +38,11 @@ if [[ $CI_NAME == 'osx' || $CI_NAME == 'darwin' ]]; then brew update dependencies=("qt5" "python" "libusb" "cmake" "doxygen") installAndUpgrade "${dependencies[@]}" +# github actions uname -> windows-2019 -> mingw64_nt-10.0-17763 +# TODO: Azure uname windows? +elif [[ $CI_NAME == *"mingw64_nt"* ]]; then + echo "Yes, we are Windows: $CI_NAME" +# Windows has no dependency manager elif [[ $CI_NAME != 'linux' ]]; then echo "Unsupported platform: $CI_NAME" exit 5 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index cebc4f1f..3cf3ebcd 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -114,6 +114,68 @@ jobs: name: macOS.zip path: macOS +###################### +###### Windows ####### +###################### + + windows: + name: Windows + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + with: + submodules: true + + # Append PR number to version + - name: Append PR number to version + shell: bash + run: | + tr -d '\n' < version > temp && mv temp version + echo -n "-PR#${{ github.event.pull_request.number }}" >> version + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.14.1' + target: 'desktop' + arch: 'win64_msvc2017_64' + + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + + - name: Install NSIS + run: | + Invoke-WebRequest https://netcologne.dl.sourceforge.net/project/nsis/NSIS%203/3.05/nsis-3.05-setup.exe -OutFile nsis-setup.exe + .\nsis-setup.exe /S + + - name: Set up x64 build architecture environment + shell: cmd + run: call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + + # Build packages + - name: Build packages + env: + VCINSTALLDIR: 'C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC' + PLATFORM: windows + shell: bash + run: ./.ci/ci_build.sh + + # Collecting deployable artifacts + - name: Collecting deployable artifacts + shell: bash + run: | + mkdir -p windows + mv build/*.zip windows + # Upload artifacts + - name: Upload artifacts + uses: actions/upload-artifact@v1 + with: + name: windows.zip + path: windows + ###################### #### Documentation ### ###################### diff --git a/.github/workflows/push-master.yml b/.github/workflows/push-master.yml index d8a9db6a..579e8d91 100644 --- a/.github/workflows/push-master.yml +++ b/.github/workflows/push-master.yml @@ -85,6 +85,42 @@ jobs: with: path: build/Hyperion-* +###################### +###### Windows ####### +###################### + + windows: + name: Windows + runs-on: windows-latest + steps: + - name: Checkout + uses: actions/checkout@v1 + with: + submodules: true + + - name: Install Qt + uses: jurplel/install-qt-action@v2 + with: + version: '5.14.1' + target: 'desktop' + arch: 'win64_msvc2017_64' + + - name: Install Python + uses: actions/setup-python@v1 + with: + python-version: '3.x' + + - name: Set up x64 build architecture environment + shell: cmd + run: call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Auxiliary\Build\vcvars64.bat" + + # Build packages + - name: Build packages + env: + PLATFORM: windows + shell: bash + run: ./.ci/ci_build.sh + ###################### #### Documentation ### ###################### diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index b821038f..506560e5 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -7,12 +7,28 @@ "/usr/include/**" ], "defines": [], - "compilerPath": "/usr/bin/gcc-5", + "compilerPath": "/usr/bin/gcc", "intelliSenseMode": "gcc-x64", "cppStandard": "c++11", "cStandard": "c11", "configurationProvider": "ms-vscode.cmake-tools" + }, + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**", + "G:/Programme/Qt/5.14.1/msvc2017_64/include/**" + ], + "defines": [ + "_DEBUG", + "UNICODE", + "_UNICODE" + ], + "cStandard": "c11", + "cppStandard": "c++11", + "intelliSenseMode": "msvc-x64", + "configurationProvider": "ms-vscode.cmake-tools" } ], "version": 4 -} \ No newline at end of file +} diff --git a/.vscode/hyperion.code-workspace b/.vscode/hyperion.code-workspace new file mode 100644 index 00000000..55ecea61 --- /dev/null +++ b/.vscode/hyperion.code-workspace @@ -0,0 +1,28 @@ +{ + "folders": [ + { + "path": "../" + } + ], + "settings": { + "editor.formatOnSave": false, + "cmake.environment": { + "Qt5_DIR":"G:/Programme/Qt/5.14.1/msvc2017_64/lib/cmake/Qt5", + "CMAKE_PREFIX_PATH": "G:/Programme/Qt/5.14.1/msvc2017_64" + }, + }, + "extensions": { + "recommendations": [ + "twxs.cmake", + "ms-vscode.cpptools", + "ms-vscode.cmake-tools", + "spmeesseman.vscode-taskexplorer", + "yzhang.markdown-all-in-one", + "formulahendry.auto-rename-tag", + "CoenraadS.bracket-pair-colorizer", + "eamodio.gitlens", + "vscode-icons-team.vscode-icons", + "editorconfig.editorconfig" + ] + } +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..909e3c07 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,38 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "(Linux) hyperiond", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/bin/hyperiond", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, + { + "name": "(Windows) hyperiond", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/build/bin/hyperiond.exe", + "args": [], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..6bd2346e --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,123 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "cmake:conf Release", + "detail": "Configure Build Env. Release/Debug switch not required for multi generators (msvc)", + "type": "shell", + "command": "cmake -B ${workspaceFolder}/build -DCMAKE_BUILD_TYPE=Release", + "windows": { + "command": "cmake -G \"Visual Studio 16 2019\" -A x64 -B ${workspaceFolder}/build" + }, + "group": "build", + }, + { + "label": "cmake:conf Debug", + "detail": "Configure Build Env. Release/Debug switch not required with msvc", + "type": "shell", + "command": "cmake -B ${workspaceFolder}/build -DCMAKE_BUILD_TYPE=Debug", + "windows": { + "command": "cmake -G \"Visual Studio 16 2019\" -A x64 -B ${workspaceFolder}/build" + }, + "group": "build", + }, + { + "label": "build:debug hyperiond", + "type": "shell", + "command": "cmake --build ${workspaceFolder}/build --target hyperiond -- -j $(nproc)", + "windows": { + "command": "cmake --build ${workspaceFolder}/build --target hyperiond --config Debug -- -maxcpucount" + }, + "osx": { + "command": "cmake --build ${workspaceFolder}/build --target hyperiond -- -j $(sysctl -n hw.ncpu)" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "build:debug all", + "type": "shell", + "command": "cmake --build ${workspaceFolder}/build -- -j $(nproc)", + "windows": { + "command": "cmake --build ${workspaceFolder}/build --config Debug -- -maxcpucount" + }, + "osx": { + "command": "cmake --build ${workspaceFolder}/build -- -j $(sysctl -n hw.ncpu)" + }, + "group": "build" + }, + { + "label": "build:debug package", + "type": "shell", + "command": "cmake --build ${workspaceFolder}/build --target package -- -j $(nproc)", + "windows": { + "command": "cmake --build ${workspaceFolder}/build --target package --config Debug -- -maxcpucount" + }, + "osx": { + "command": "cmake --build ${workspaceFolder}/build --target package -- -j $(sysctl -n hw.ncpu)" + }, + "group": "build" + }, + { + "label": "build:release hyperiond", + "type": "shell", + "command": "cmake --build ${workspaceFolder}/build --target hyperiond -- -j $(nproc)", + "windows": { + "command": "cmake --build ${workspaceFolder}/build --target hyperiond --config Release -- -maxcpucount" + }, + "osx": { + "command": "cmake --build ${workspaceFolder}/build --target hyperiond -- -j $(sysctl -n hw.ncpu)" + }, + "group": { + "kind": "build", + "isDefault": true + } + }, + { + "label": "build:release all", + "type": "shell", + "command": "cmake --build ${workspaceFolder}/build -- -j $(nproc)", + "windows": { + "command": "cmake --build ${workspaceFolder}/build --config Release -- -maxcpucount" + }, + "osx": { + "command": "cmake --build ${workspaceFolder}/build -- -j $(sysctl -n hw.ncpu)" + }, + "group": "build" + }, + { + "label": "build:release package", + "type": "shell", + "command": "cmake --build ${workspaceFolder}/build --target package -- -j $(nproc)", + "windows": { + "command": "cmake --build ${workspaceFolder}/build --target package --config Release -- -maxcpucount" + }, + "osx": { + "command": "cmake --build ${workspaceFolder}/build --target package -- -j $(sysctl -n hw.ncpu)" + }, + "group": "build" + }, + { + "label": "run:hyperiond (Debug)", + "type": "shell", + "command": "${workspaceFolder}/build/bin/hyperiond -d", + "windows": { + "command": "${workspaceFolder}/build/bin/Debug/hyperiond" + }, + "group": "build" + }, + { + "label": "run:hyperiond (Release)", + "type": "shell", + "command": "${workspaceFolder}/build/bin/hyperiond -d", + "windows": { + "command": "${workspaceFolder}/build/bin/Release/hyperiond" + }, + "group": "build" + } + ] +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 32b7a65e..c96ade6f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,8 +25,15 @@ if ( CCACHE_FOUND ) set_property(GLOBAL PROPERTY RULE_LAUNCH_LINK ccache) endif(CCACHE_FOUND) -set(Python_ADDITIONAL_VERSIONS 3.5) -find_package( PythonInterp 3.5 REQUIRED ) +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + if(Python_FOUND) + set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE}) + endif() +else() + set(Python_ADDITIONAL_VERSIONS 3.5) + find_package( PythonInterp 3.5 REQUIRED ) +endif() # Set build variables SET ( DEFAULT_AMLOGIC OFF ) @@ -35,6 +42,7 @@ SET ( DEFAULT_OSX OFF ) SET ( DEFAULT_X11 OFF ) SET ( DEFAULT_QT ON ) SET ( DEFAULT_WS281XPWM OFF ) +SET ( DEFAULT_AVAHI ON ) SET ( DEFAULT_USE_SHARED_AVAHI_LIBS ON ) SET ( DEFAULT_USE_SYSTEM_FLATBUFFERS_LIBS OFF ) SET ( DEFAULT_USE_SYSTEM_PROTO_LIBS OFF ) @@ -43,12 +51,14 @@ SET ( DEFAULT_TESTS OFF ) IF ( ${CMAKE_SYSTEM} MATCHES "Linux" ) SET ( DEFAULT_V4L2 ON ) SET ( DEFAULT_SPIDEV ON ) + SET ( DEFAULT_TINKERFORGE ON) SET ( DEFAULT_FB ON ) SET ( DEFAULT_USB_HID ON ) ELSE() SET ( DEFAULT_V4L2 OFF ) SET ( DEFAULT_FB OFF ) SET ( DEFAULT_SPIDEV OFF ) + SET ( DEFAULT_TINKERFORGE OFF) SET ( DEFAULT_USB_HID OFF ) ENDIF() @@ -114,8 +124,8 @@ elseif ( "${PLATFORM}" MATCHES "x11" ) endif() elseif ( "${PLATFORM}" STREQUAL "imx6" ) SET ( DEFAULT_FB ON ) -elseif ( "${PLATFORM}" STREQUAL "windows" ) - MESSAGE( WARNING "Hyperion is not developed nor tested on MS Windows.") +elseif (WIN32) + SET ( DEFAULT_AVAHI OFF) endif() # enable tests for -dev builds @@ -147,7 +157,7 @@ message(STATUS "ENABLE_OSX = ${ENABLE_OSX}") option(ENABLE_SPIDEV "Enable the SPIDEV device" ${DEFAULT_SPIDEV} ) message(STATUS "ENABLE_SPIDEV = ${ENABLE_SPIDEV}") -option(ENABLE_TINKERFORGE "Enable the TINKERFORGE device" ON) +option(ENABLE_TINKERFORGE "Enable the TINKERFORGE device" ${DEFAULT_TINKERFORGE}) message(STATUS "ENABLE_TINKERFORGE = ${ENABLE_TINKERFORGE}") option(ENABLE_V4L2 "Enable the V4L2 grabber" ${DEFAULT_V4L2}) @@ -156,6 +166,9 @@ message(STATUS "ENABLE_V4L2 = ${ENABLE_V4L2}") option(ENABLE_WS281XPWM "Enable the WS281x-PWM device" ${DEFAULT_WS281XPWM} ) message(STATUS "ENABLE_WS281XPWM = ${ENABLE_WS281XPWM}") +option(ENABLE_AVAHI "Enable Zeroconf" ${DEFAULT_AVAHI}) +message(STATUS "ENABLE_AVAHI = " ${ENABLE_AVAHI}) + option(ENABLE_USB_HID "Enable the libusb and hid devices" ${DEFAULT_USB_HID} ) message(STATUS "ENABLE_USB_HID = ${ENABLE_USB_HID}") @@ -183,6 +196,7 @@ SET( JSON_FILES config/hyperion.config.json.default ${HYPERION_SCHEMAS} ) + EXECUTE_PROCESS ( COMMAND ${PYTHON_EXECUTABLE} test/jsonchecks/checkjson.py ${JSON_FILES} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} @@ -202,11 +216,14 @@ IF ( ${CHECK_EFFECTS_FAILED} ) ENDIF () # for python 3 the checkschema.py file must be rewritten -EXECUTE_PROCESS ( - COMMAND python test/jsonchecks/checkschema.py config/hyperion.config.json.default libsrc/hyperion/hyperion.schema.json - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - RESULT_VARIABLE CHECK_CONFIG_FAILED -) +# TODO on windows it can't resolve the path inside the file (Das System kann den angegebenen Pfad nicht finden: '\\schema\\schema-general.json') +IF (NOT WIN32) + EXECUTE_PROCESS ( + COMMAND python test/jsonchecks/checkschema.py config/hyperion.config.json.default libsrc/hyperion/hyperion.schema.json + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + RESULT_VARIABLE CHECK_CONFIG_FAILED + ) +ENDIF() IF ( ${CHECK_CONFIG_FAILED} ) MESSAGE (FATAL_ERROR "check of json default config failed" ) ENDIF () @@ -235,29 +252,61 @@ include_directories(${CMAKE_SOURCE_DIR}/include) # Prefer static linking over dynamic #set(CMAKE_FIND_LIBRARY_SUFFIXES ".a;.so") -# enable C++11 -include(CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) -CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) +# enable C++11; MSVC doesn't have c++11 feature switch +if(NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + include(CheckCXXCompilerFlag) + CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) + CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") -if (CMAKE_COMPILER_IS_GNUCXX) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-psabi") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall") + if (CMAKE_COMPILER_IS_GNUCXX) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-psabi") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-psabi") + endif() + if(COMPILER_SUPPORTS_CXX11) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + elseif(COMPILER_SUPPORTS_CXX0X) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") + else() + message(STATUS "No support for C++11 detected. Compilation will most likely fail on your compiler") + endif() endif() -if(COMPILER_SUPPORTS_CXX11) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") -elseif(COMPILER_SUPPORTS_CXX0X) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x") -else() - message(STATUS "No support for C++11 detected. Compilation will most likely fail on your compiler") + +# MSVC options +if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + # Search for Windows SDK + find_package(WindowsSDK REQUIRED) + message(STATUS "WINDOWS SDK: ${WINDOWSSDK_LATEST_DIR} ${WINDOWSSDK_LATEST_NAME}") + message(STATUS "MSVC VERSION: ${MSVC_VERSION}") + + # Qt5 default install path with msvc2017 64bit component + # The Qt5_DIR should point to Qt5Config.cmake -> C:/Qt/5.xx/msvc2017_64/lib/cmake/Qt5 + # The CMAKE_PREFIX_PATH should point to the install directory -> C:/Qt/5.xx/msvc2017_64 + FIRSTSUBDIR(SUBDIRQT "C:/Qt") + SET(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "${SUBDIRQT}/msvc2017_64") + if (NOT DEFINED ENV{Qt5_DIR}) + message(STATUS "Set Qt5_DIR to default install path C:/Qt") + SET(Qt5_DIR "${SUBDIRQT}/msvc2017_64/lib/cmake/Qt5") + endif() +endif() + +# Windows specific +if(WIN32) + # Path to .rc icon file for add_executable() calls + SET ( WIN_RC_ICON_FILE ${CMAKE_SOURCE_DIR}/cmake/nsis/icon.rc) + # Force gui app + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") endif() # Use GNU gold linker if available -include (${CMAKE_CURRENT_SOURCE_DIR}/cmake/LDGold.cmake) +if (NOT WIN32) + include (${CMAKE_CURRENT_SOURCE_DIR}/cmake/LDGold.cmake) +endif() # Don't create new dynamic tags (RUNPATH) -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-new-dtags") +if (NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--disable-new-dtags") +endif() # setup -rpath to search for shared libs in BINARY/../lib folder if (UNIX AND NOT APPLE) @@ -318,7 +367,7 @@ if (ENABLE_V4L2) endif() endif (TURBOJPEG_FOUND) - + if (TURBOJPEG_FOUND OR JPEG_FOUND) add_definitions(-DHAVE_JPEG_DECODER) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7883262..6c45c416 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,10 +3,7 @@ You can participate in the translation. [![Join Translation](https://img.shields.io/badge/POEditor-translate-green.svg)](https://poeditor.com/join/project/Y4F6vHRFjA) ## Development Setup - -> TODO: Be more specific, provide a Visual Studio Code workspace with plugins and pre configured build tasks - -Get Hyperion to compile: [Compile HowTo](CompileHowTo.md) +You may already have an editor or IDE you want to use. In any case we provide a pre configured [Visual Studio Code](#visual-studio-code) setup. ## Workflow @@ -120,4 +117,16 @@ The amount of "%" mus match with following arguments ## Commit specification -> TODO \ No newline at end of file +> TODO + +## Visual Studio Code +**We assume that you sucessfully compiled Hyperion with the [Compile HowTo](CompileHowTo.md) WITHOUT Docker** \ +If you want to use VSCode for development follow the steps. + +- Install [VSCode](https://code.visualstudio.com/). On Ubuntu 16.04+ you can also use the [Snapcraft VSCode](https://snapcraft.io/code) package. +- Linux: Install gdb `sudo apt-get install gdb` +- Mac: ? +- Open VSCode and click on _File_ -> _Open Workspace_ and select the file `hyperion.ng/.vscode/hyperion.code-workspace` +- Install recommended extensions +- If you installed the Task Explorer you can now use the defined vscode tasks to build Hyperion and configure cmake +- For debugging you need to build Hyperion in Debug mode and start the correct Run config diff --git a/CompileHowto.md b/CompileHowto.md index ff4101ec..34ba37ec 100644 --- a/CompileHowto.md +++ b/CompileHowto.md @@ -74,6 +74,16 @@ brew install libusb brew install doxygen ``` +## Windows (WIP) +We assume a 64bit Windows 7 or higher. Install the following +- [Git](https://git-scm.com/downloads) (Check: Add to PATH) +- [Python 3 (Windows x86-64 executable installer)](https://www.python.org/downloads/windows/) (Check: Add to PATH and Debug Symbols) +- [CMake (Windows win64-x64 Installer)](https://cmake.org/download/) (Check: Add to PATH) +- [Qt5](https://www.qt.io/download-open-source) (Check: Component install: msvc 2017 64bit). No configuration for PATH is set, we assume the default install path. +- [Visual Studio 2019 Build Tools](https://go.microsoft.com/fwlink/?linkid=840931) + - The Visual Studio 2019 Community Editor is not required but will be installed as part of the compiler and Windows SDK + - Select C++ Desktop Development Tools + - Now just select `MSVC v142 VS 2019 C++ x64/x86-Buildtools`, `C++-CMake Tools for Windows` and `Windows 10 SDK`. Everything else is not needed. # Compiling and installing Hyperion @@ -148,6 +158,13 @@ Platform should be auto detected and refer to osx, you can also force osx: cmake -DPLATFORM=osx -DCMAKE_BUILD_TYPE=Release .. ``` +To generate files on Windows (Release+Debug capable): + +Platform should be auto detected and refer to windows, you can also force windows: +``` +cmake -DPLATFORM=windows -G "Windows 16 2019" .. +``` + ### Run make to build Hyperion The `-j $(nproc)` specifies the amount of CPU cores to use. ```bash @@ -160,6 +177,11 @@ On a mac you can use ``sysctl -n hw.ncpu`` to get the number of available CPU co make -j $(sysctl -n hw.ncpu) ``` +On Windows run +```bash +cmake --build . --config Release -- -maxcpucount +``` + ### Install hyperion into your system Copy all necessary files to ``/usr/local/share/hyperion`` diff --git a/HyperionConfig.h.in b/HyperionConfig.h.in index 6b3804ae..1bed97d3 100644 --- a/HyperionConfig.h.in +++ b/HyperionConfig.h.in @@ -30,6 +30,9 @@ // Define to enable the tinkerforge device #cmakedefine ENABLE_TINKERFORGE +// Define to enable avahi +#cmakedefine ENABLE_AVAHI + // Define to enable the usb / hid devices #cmakedefine ENABLE_USB_HID diff --git a/README.md b/README.md index bd95ae57..e56db398 100644 --- a/README.md +++ b/README.md @@ -23,22 +23,31 @@ * A scriptable (Python) effect engine * A multi language web interface to configure and remote control hyperion -If you need further support please open a topic at the forum! +If you need further support please open a topic at the forum! [![Hyperion webpage/forum](https://img.shields.io/website/https/hyperion-project.org.svg?down_color=red&down_message=offline&up_color=green&up_message=online)](https://www.hyperion-project.org) -## Contributing +## Contributing -Contributions are welcome! Feel free to join us! We are looking always for people who wants to participate. +Contributions are welcome! Feel free to join us! We are looking always for people who wants to participate. [![Contributors](https://img.shields.io/github/contributors/hyperion-project/hyperion.ng.svg)](https://github.com/hyperion-project/hyperion.ng/graphs/contributors) -For an example, you can participate in the translation. +For an example, you can participate in the translation. [![Join Translation](https://img.shields.io/badge/POEditor-translate-green.svg)](https://poeditor.com/join/project/Y4F6vHRFjA) ## Requirements -Debian 9, Ubuntu 16.04 or higher. Windows is not supported currently. +Debian 9, Ubuntu 16.04 or higher. We provide a macOS Build but we can not support this. +## Documentation +Covers these topics (WorkInProgress) +- Installtion +- Configuration +- Effect development +- JSON API + +[Visit Documentation](https://docs.hyperion-project.org) + ## Building See [CompileHowto](CompileHowto.md) and [CrossCompileHowto](CrossCompileHowto.md). @@ -46,6 +55,6 @@ See [CompileHowto](CompileHowto.md) and [CrossCompileHowto](CrossCompileHowto.md Alpha releases available from the [Hyperion release page](https://github.com/hyperion-project/hyperion.ng/releases) ## License -The source is released under MIT-License (see http://opensource.org/licenses/MIT). +The source is released under MIT-License (see http://opensource.org/licenses/MIT). [![GitHub license](https://img.shields.io/badge/License-MIT-yellow.svg)](https://raw.githubusercontent.com/hyperion-project/hyperion.ng/master/LICENSE) diff --git a/assets/webconfig/js/ui_utils.js b/assets/webconfig/js/ui_utils.js index 25f10a43..60b4e283 100644 --- a/assets/webconfig/js/ui_utils.js +++ b/assets/webconfig/js/ui_utils.js @@ -62,7 +62,7 @@ function debugMessage(msg) function updateSessions() { var sess = window.serverInfo.sessions; - if (sess.length) + if (sess && sess.length) { window.wSess = []; for(var i = 0; i= 3.12 implemented + set(url "https://www.python.org/ftp/python/${Python3_VERSION}/") + set(filename "python-${Python3_VERSION}-embed-amd64.zip") + + if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${filename}" OR NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/python") + file(DOWNLOAD "${url}${filename}" "${CMAKE_CURRENT_BINARY_DIR}/${filename}" + STATUS result + ) + + # Check if the download is successful + list(GET result 0 result_code) + if(NOT result_code EQUAL 0) + list(GET result 1 reason) + message(FATAL_ERROR "Could not download file ${url}${filename}: ${reason}") + endif() + + # Unpack downloaded embed python + file(REMOVE_RECURSE ${CMAKE_CURRENT_BINARY_DIR}/python) + file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/python) + execute_process( + COMMAND ${CMAKE_COMMAND} -E tar -xfz "${CMAKE_CURRENT_BINARY_DIR}/${filename}" + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/python + OUTPUT_QUIET + ) + endif() + + # Copy pythonXX.dll and pythonXX.zip to 'hyperion' + foreach(PYTHON_FILE + "python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}.dll" + "python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}.zip" + ) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/python/${PYTHON_FILE} + DESTINATION "bin" + COMPONENT "Hyperion" + ) + endforeach() + + else() + # Run CMake after target was built + add_custom_command( + TARGET ${TARGET} POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS ${CMAKE_SOURCE_DIR} + WORKING_DIRECTORY ${CMAKE_BINARY_DIR} + VERBATIM + ) + endif() +endmacro() diff --git a/cmake/FindWindowsSDK.cmake b/cmake/FindWindowsSDK.cmake new file mode 100644 index 00000000..747d9bc4 --- /dev/null +++ b/cmake/FindWindowsSDK.cmake @@ -0,0 +1,635 @@ +# - Find the Windows SDK aka Platform SDK +# +# Relevant Wikipedia article: http://en.wikipedia.org/wiki/Microsoft_Windows_SDK +# +# Pass "COMPONENTS tools" to ignore Visual Studio version checks: in case +# you just want the tool binaries to run, rather than the libraries and headers +# for compiling. +# +# Variables: +# WINDOWSSDK_FOUND - if any version of the windows or platform SDK was found that is usable with the current version of visual studio +# WINDOWSSDK_LATEST_DIR +# WINDOWSSDK_LATEST_NAME +# WINDOWSSDK_FOUND_PREFERENCE - if we found an entry indicating a "preferred" SDK listed for this visual studio version +# WINDOWSSDK_PREFERRED_DIR +# WINDOWSSDK_PREFERRED_NAME +# +# WINDOWSSDK_DIRS - contains no duplicates, ordered most recent first. +# WINDOWSSDK_PREFERRED_FIRST_DIRS - contains no duplicates, ordered with preferred first, followed by the rest in descending recency +# +# Functions: +# windowssdk_name_lookup( ) - Find the name corresponding with the SDK directory you pass in, or +# NOTFOUND if not recognized. Your directory must be one of WINDOWSSDK_DIRS for this to work. +# +# windowssdk_build_lookup( ) - Find the build version number corresponding with the SDK directory you pass in, or +# NOTFOUND if not recognized. Your directory must be one of WINDOWSSDK_DIRS for this to work. +# +# get_windowssdk_from_component( ) - Given a library or include dir, +# find the Windows SDK root dir corresponding to it, or NOTFOUND if unrecognized. +# +# get_windowssdk_library_dirs( ) - Find the architecture-appropriate +# library directories corresponding to the SDK directory you pass in (or NOTFOUND if none) +# +# get_windowssdk_library_dirs_multiple( ...) - Find the architecture-appropriate +# library directories corresponding to the SDK directories you pass in, in order, skipping those not found. NOTFOUND if none at all. +# Good for passing WINDOWSSDK_DIRS or WINDOWSSDK_DIRS to if you really just want a file and don't care where from. +# +# get_windowssdk_include_dirs( ) - Find the +# include directories corresponding to the SDK directory you pass in (or NOTFOUND if none) +# +# get_windowssdk_include_dirs_multiple( ...) - Find the +# include directories corresponding to the SDK directories you pass in, in order, skipping those not found. NOTFOUND if none at all. +# Good for passing WINDOWSSDK_DIRS or WINDOWSSDK_DIRS to if you really just want a file and don't care where from. +# +# Requires these CMake modules: +# FindPackageHandleStandardArgs (known included with CMake >=2.6.2) +# +# Original Author: +# 2012 Ryan Pavlik +# http://academic.cleardefinition.com +# Iowa State University HCI Graduate Program/VRAC +# +# Copyright Iowa State University 2012. +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +set(_preferred_sdk_dirs) # pre-output +set(_win_sdk_dirs) # pre-output +set(_win_sdk_versanddirs) # pre-output +set(_win_sdk_buildsanddirs) # pre-output +set(_winsdk_vistaonly) # search parameters +set(_winsdk_kits) # search parameters + + +set(_WINDOWSSDK_ANNOUNCE OFF) +if(NOT WINDOWSSDK_FOUND AND (NOT WindowsSDK_FIND_QUIETLY)) + set(_WINDOWSSDK_ANNOUNCE ON) +endif() +macro(_winsdk_announce) + if(_WINSDK_ANNOUNCE) + message(STATUS ${ARGN}) + endif() +endmacro() + +# See https://developer.microsoft.com/en-us/windows/downloads/sdk-archive - +# although version numbers listed on that page don't necessarily match the directory +# used by the installer. +set(_winsdk_win10vers + 10.0.18362.0 # Win10 1903 "19H1" + 10.0.17763.0 # Win10 1809 "October 2018 Update" + 10.0.17134.0 # Redstone 4 aka Win10 1803 "April 2018 Update" + 10.0.17133.0 # Redstone 4 aka Win10 1803 "April 2018 Update" + 10.0.16299.0 # Redstone 3 aka Win10 1709 "Fall Creators Update" + 10.0.15063.0 # Redstone 2 aka Win10 1703 "Creators Update" + 10.0.14393.0 # Redstone aka Win10 1607 "Anniversary Update" + 10.0.10586.0 # TH2 aka Win10 1511 + 10.0.10240.0 # Win10 RTM + 10.0.10150.0 # just ucrt + 10.0.10056.0 +) + +if(WindowsSDK_FIND_COMPONENTS MATCHES "tools") + set(_WINDOWSSDK_IGNOREMSVC ON) + _winsdk_announce("Checking for tools from Windows/Platform SDKs...") +else() + set(_WINDOWSSDK_IGNOREMSVC OFF) + _winsdk_announce("Checking for Windows/Platform SDKs...") +endif() + +# Appends to the three main pre-output lists used only if the path exists +# and is not already in the list. +function(_winsdk_conditional_append _vername _build _path) + if(("${_path}" MATCHES "registry") OR (NOT EXISTS "${_path}")) + # Path invalid - do not add + return() + endif() + list(FIND _win_sdk_dirs "${_path}" _win_sdk_idx) + if(_win_sdk_idx GREATER -1) + # Path already in list - do not add + return() + endif() + _winsdk_announce( " - ${_vername}, Build ${_build} @ ${_path}") + # Not yet in the list, so we'll add it + list(APPEND _win_sdk_dirs "${_path}") + set(_win_sdk_dirs "${_win_sdk_dirs}" CACHE INTERNAL "" FORCE) + list(APPEND + _win_sdk_versanddirs + "${_vername}" + "${_path}") + set(_win_sdk_versanddirs "${_win_sdk_versanddirs}" CACHE INTERNAL "" FORCE) + list(APPEND + _win_sdk_buildsanddirs + "${_build}" + "${_path}") + set(_win_sdk_buildsanddirs "${_win_sdk_buildsanddirs}" CACHE INTERNAL "" FORCE) +endfunction() + +# Appends to the "preferred SDK" lists only if the path exists +function(_winsdk_conditional_append_preferred _info _path) + if(("${_path}" MATCHES "registry") OR (NOT EXISTS "${_path}")) + # Path invalid - do not add + return() + endif() + + get_filename_component(_path "${_path}" ABSOLUTE) + + list(FIND _win_sdk_preferred_sdk_dirs "${_path}" _win_sdk_idx) + if(_win_sdk_idx GREATER -1) + # Path already in list - do not add + return() + endif() + _winsdk_announce( " - Found \"preferred\" SDK ${_info} @ ${_path}") + # Not yet in the list, so we'll add it + list(APPEND _win_sdk_preferred_sdk_dirs "${_path}") + set(_win_sdk_preferred_sdk_dirs "${_win_sdk_dirs}" CACHE INTERNAL "" FORCE) + + # Just in case we somehow missed it: + _winsdk_conditional_append("${_info}" "" "${_path}") +endfunction() + +# Given a version like v7.0A, looks for an SDK in the registry under "Microsoft SDKs". +# If the given version might be in both HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows +# and HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots aka "Windows Kits", +# use this macro first, since these registry keys usually have more information. +# +# Pass a "default" build number as an extra argument in case we can't find it. +function(_winsdk_check_microsoft_sdks_registry _winsdkver) + set(SDKKEY "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\${_winsdkver}") + get_filename_component(_sdkdir + "[${SDKKEY};InstallationFolder]" + ABSOLUTE) + + set(_sdkname "Windows SDK ${_winsdkver}") + + # Default build number passed as extra argument + set(_build ${ARGN}) + # See if the registry holds a Microsoft-mutilated, err, designated, product name + # (just using get_filename_component to execute the registry lookup) + get_filename_component(_sdkproductname + "[${SDKKEY};ProductName]" + NAME) + if(NOT "${_sdkproductname}" MATCHES "registry") + # Got a product name + set(_sdkname "${_sdkname} (${_sdkproductname})") + endif() + + # try for a version to augment our name + # (just using get_filename_component to execute the registry lookup) + get_filename_component(_sdkver + "[${SDKKEY};ProductVersion]" + NAME) + if(NOT "${_sdkver}" MATCHES "registry" AND NOT MATCHES) + # Got a version + if(NOT "${_sdkver}" MATCHES "\\.\\.") + # and it's not an invalid one with two dots in it: + # use to override the default build + set(_build ${_sdkver}) + if(NOT "${_sdkname}" MATCHES "${_sdkver}") + # Got a version that's not already in the name, let's use it to improve our name. + set(_sdkname "${_sdkname} (${_sdkver})") + endif() + endif() + endif() + _winsdk_conditional_append("${_sdkname}" "${_build}" "${_sdkdir}") +endfunction() + +# Given a name for identification purposes, the build number, and a key (technically a "value name") +# corresponding to a Windows SDK packaged as a "Windows Kit", look for it +# in HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots +# Note that the key or "value name" tends to be something weird like KitsRoot81 - +# no easy way to predict, just have to observe them in the wild. +# Doesn't hurt to also try _winsdk_check_microsoft_sdks_registry for these: +# sometimes you get keys in both parts of the registry (in the wow64 portion especially), +# and the non-"Windows Kits" location is often more descriptive. +function(_winsdk_check_windows_kits_registry _winkit_name _winkit_build _winkit_key) + get_filename_component(_sdkdir + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;${_winkit_key}]" + ABSOLUTE) + _winsdk_conditional_append("${_winkit_name}" "${_winkit_build}" "${_sdkdir}") +endfunction() + +# Given a name for identification purposes and the build number +# corresponding to a Windows 10 SDK packaged as a "Windows Kit", look for it +# in HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots +# Doesn't hurt to also try _winsdk_check_microsoft_sdks_registry for these: +# sometimes you get keys in both parts of the registry (in the wow64 portion especially), +# and the non-"Windows Kits" location is often more descriptive. +function(_winsdk_check_win10_kits _winkit_build) + get_filename_component(_sdkdir + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;KitsRoot10]" + ABSOLUTE) + if(("${_sdkdir}" MATCHES "registry") OR (NOT EXISTS "${_sdkdir}")) + return() # not found + endif() + if(EXISTS "${_sdkdir}/Include/${_winkit_build}/um") + _winsdk_conditional_append("Windows Kits 10 (Build ${_winkit_build})" "${_winkit_build}" "${_sdkdir}") + endif() +endfunction() + +# Given a name for indentification purposes, the build number, and the associated package GUID, +# look in the registry under both HKLM and HKCU in \\SOFTWARE\\Microsoft\\MicrosoftSDK\\InstalledSDKs\\ +# for that guid and the SDK it points to. +function(_winsdk_check_platformsdk_registry _platformsdkname _build _platformsdkguid) + foreach(_winsdk_hive HKEY_LOCAL_MACHINE HKEY_CURRENT_USER) + get_filename_component(_sdkdir + "[${_winsdk_hive}\\SOFTWARE\\Microsoft\\MicrosoftSDK\\InstalledSDKs\\${_platformsdkguid};Install Dir]" + ABSOLUTE) + _winsdk_conditional_append("${_platformsdkname} (${_build})" "${_build}" "${_sdkdir}") + endforeach() +endfunction() + +### +# Detect toolchain information: to know whether it's OK to use Vista+ only SDKs +### +set(_winsdk_vistaonly_ok OFF) +if(MSVC AND NOT _WINDOWSSDK_IGNOREMSVC) + # VC 10 and older has broad target support + if(MSVC_VERSION LESS 1700) + # VC 11 by default targets Vista and later only, so we can add a few more SDKs that (might?) only work on vista+ + elseif("${CMAKE_VS_PLATFORM_TOOLSET}" MATCHES "_xp") + # This is the XP-compatible v110+ toolset + elseif("${CMAKE_VS_PLATFORM_TOOLSET}" STREQUAL "v100" OR "${CMAKE_VS_PLATFORM_TOOLSET}" STREQUAL "v90") + # This is the VS2010/VS2008 toolset + else() + # OK, we're VC11 or newer and not using a backlevel or XP-compatible toolset. + # These versions have no XP (and possibly Vista pre-SP1) support + set(_winsdk_vistaonly_ok ON) + if(_WINDOWSSDK_ANNOUNCE AND NOT _WINDOWSSDK_VISTAONLY_PESTERED) + set(_WINDOWSSDK_VISTAONLY_PESTERED ON CACHE INTERNAL "" FORCE) + message(STATUS "FindWindowsSDK: Detected Visual Studio 2012 or newer, not using the _xp toolset variant: including SDK versions that drop XP support in search!") + endif() + endif() +endif() +if(_WINDOWSSDK_IGNOREMSVC) + set(_winsdk_vistaonly_ok ON) +endif() + +### +# MSVC version checks - keeps messy conditionals in one place +# (messy because of _WINDOWSSDK_IGNOREMSVC) +### +set(_winsdk_msvc_greater_1200 OFF) +if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION GREATER 1200))) + set(_winsdk_msvc_greater_1200 ON) +endif() +# Newer than VS .NET/VS Toolkit 2003 +set(_winsdk_msvc_greater_1310 OFF) +if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION GREATER 1310))) + set(_winsdk_msvc_greater_1310 ON) +endif() + +# VS2005/2008 +set(_winsdk_msvc_less_1600 OFF) +if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION LESS 1600))) + set(_winsdk_msvc_less_1600 ON) +endif() + +# VS2013+ +set(_winsdk_msvc_not_less_1800 OFF) +if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (NOT MSVC_VERSION LESS 1800))) + set(_winsdk_msvc_not_less_1800 ON) +endif() + +### +# START body of find module +### +if(_winsdk_msvc_greater_1310) # Newer than VS .NET/VS Toolkit 2003 + ### + # Look for "preferred" SDKs + ### + + # Environment variable for SDK dir + if(EXISTS "$ENV{WindowsSDKDir}" AND (NOT "$ENV{WindowsSDKDir}" STREQUAL "")) + _winsdk_conditional_append_preferred("WindowsSDKDir environment variable" "$ENV{WindowsSDKDir}") + endif() + + if(_winsdk_msvc_less_1600) + # Per-user current Windows SDK for VS2005/2008 + get_filename_component(_sdkdir + "[HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]" + ABSOLUTE) + _winsdk_conditional_append_preferred("Per-user current Windows SDK" "${_sdkdir}") + + # System-wide current Windows SDK for VS2005/2008 + get_filename_component(_sdkdir + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]" + ABSOLUTE) + _winsdk_conditional_append_preferred("System-wide current Windows SDK" "${_sdkdir}") + endif() + + ### + # Begin the massive list of SDK searching! + ### + if(_winsdk_vistaonly_ok AND _winsdk_msvc_not_less_1800) + # These require at least Visual Studio 2013 (VC12) + + _winsdk_check_microsoft_sdks_registry(v10.0A) + + # Windows Software Development Kit (SDK) for Windows 10 + # Several different versions living in the same directory - if nothing else we can assume RTM (10240) + _winsdk_check_microsoft_sdks_registry(v10.0 10.0.10240.0) + foreach(_win10build ${_winsdk_win10vers}) + _winsdk_check_win10_kits(${_win10build}) + endforeach() + endif() # vista-only and 2013+ + + # Included in Visual Studio 2013 + # Includes the v120_xp toolset + _winsdk_check_microsoft_sdks_registry(v8.1A 8.1.51636) + + if(_winsdk_vistaonly_ok AND _winsdk_msvc_not_less_1800) + # Windows Software Development Kit (SDK) for Windows 8.1 + # http://msdn.microsoft.com/en-gb/windows/desktop/bg162891 + _winsdk_check_microsoft_sdks_registry(v8.1 8.1.25984.0) + _winsdk_check_windows_kits_registry("Windows Kits 8.1" 8.1.25984.0 KitsRoot81) + endif() # vista-only and 2013+ + + if(_winsdk_vistaonly_ok) + # Included in Visual Studio 2012 + _winsdk_check_microsoft_sdks_registry(v8.0A 8.0.50727) + + # Microsoft Windows SDK for Windows 8 and .NET Framework 4.5 + # This is the first version to also include the DirectX SDK + # http://msdn.microsoft.com/en-US/windows/desktop/hh852363.aspx + _winsdk_check_microsoft_sdks_registry(v8.0 6.2.9200.16384) + _winsdk_check_windows_kits_registry("Windows Kits 8.0" 6.2.9200.16384 KitsRoot) + endif() # vista-only + + # Included with VS 2012 Update 1 or later + # Introduces v110_xp toolset + _winsdk_check_microsoft_sdks_registry(v7.1A 7.1.51106) + if(_winsdk_vistaonly_ok) + # Microsoft Windows SDK for Windows 7 and .NET Framework 4 + # http://www.microsoft.com/downloads/en/details.aspx?FamilyID=6b6c21d2-2006-4afa-9702-529fa782d63b + _winsdk_check_microsoft_sdks_registry(v7.1 7.1.7600.0.30514) + endif() # vista-only + + # Included with VS 2010 + _winsdk_check_microsoft_sdks_registry(v7.0A 6.1.7600.16385) + + # Windows SDK for Windows 7 and .NET Framework 3.5 SP1 + # Works with VC9 + # http://www.microsoft.com/en-us/download/details.aspx?id=18950 + _winsdk_check_microsoft_sdks_registry(v7.0 6.1.7600.16385) + + # Two versions call themselves "v6.1": + # Older: + # Windows Vista Update & .NET 3.0 SDK + # http://www.microsoft.com/en-us/download/details.aspx?id=14477 + + # Newer: + # Windows Server 2008 & .NET 3.5 SDK + # may have broken VS9SP1? they recommend v7.0 instead, or a KB... + # http://www.microsoft.com/en-us/download/details.aspx?id=24826 + _winsdk_check_microsoft_sdks_registry(v6.1 6.1.6000.16384.10) + + # Included in VS 2008 + _winsdk_check_microsoft_sdks_registry(v6.0A 6.1.6723.1) + + # Microsoft Windows Software Development Kit for Windows Vista and .NET Framework 3.0 Runtime Components + # http://blogs.msdn.com/b/stanley/archive/2006/11/08/microsoft-windows-software-development-kit-for-windows-vista-and-net-framework-3-0-runtime-components.aspx + _winsdk_check_microsoft_sdks_registry(v6.0 6.0.6000.16384) +endif() + +# Let's not forget the Platform SDKs, which sometimes are useful! +if(_winsdk_msvc_greater_1200) + _winsdk_check_platformsdk_registry("Microsoft Platform SDK for Windows Server 2003 R2" "5.2.3790.2075.51" "D2FF9F89-8AA2-4373-8A31-C838BF4DBBE1") + _winsdk_check_platformsdk_registry("Microsoft Platform SDK for Windows Server 2003 SP1" "5.2.3790.1830.15" "8F9E5EF3-A9A5-491B-A889-C58EFFECE8B3") +endif() +### +# Finally, look for "preferred" SDKs +### +if(_winsdk_msvc_greater_1310) # Newer than VS .NET/VS Toolkit 2003 + + + # Environment variable for SDK dir + if(EXISTS "$ENV{WindowsSDKDir}" AND (NOT "$ENV{WindowsSDKDir}" STREQUAL "")) + _winsdk_conditional_append_preferred("WindowsSDKDir environment variable" "$ENV{WindowsSDKDir}") + endif() + + if(_winsdk_msvc_less_1600) + # Per-user current Windows SDK for VS2005/2008 + get_filename_component(_sdkdir + "[HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]" + ABSOLUTE) + _winsdk_conditional_append_preferred("Per-user current Windows SDK" "${_sdkdir}") + + # System-wide current Windows SDK for VS2005/2008 + get_filename_component(_sdkdir + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]" + ABSOLUTE) + _winsdk_conditional_append_preferred("System-wide current Windows SDK" "${_sdkdir}") + endif() +endif() + + +function(windowssdk_name_lookup _dir _outvar) + list(FIND _win_sdk_versanddirs "${_dir}" _diridx) + math(EXPR _idx "${_diridx} - 1") + if(${_idx} GREATER -1) + list(GET _win_sdk_versanddirs ${_idx} _ret) + else() + set(_ret "NOTFOUND") + endif() + set(${_outvar} "${_ret}" PARENT_SCOPE) +endfunction() + +function(windowssdk_build_lookup _dir _outvar) + list(FIND _win_sdk_buildsanddirs "${_dir}" _diridx) + math(EXPR _idx "${_diridx} - 1") + if(${_idx} GREATER -1) + list(GET _win_sdk_buildsanddirs ${_idx} _ret) + else() + set(_ret "NOTFOUND") + endif() + set(${_outvar} "${_ret}" PARENT_SCOPE) +endfunction() + +# If we found something... +if(_win_sdk_dirs) + list(GET _win_sdk_dirs 0 WINDOWSSDK_LATEST_DIR) + windowssdk_name_lookup("${WINDOWSSDK_LATEST_DIR}" + WINDOWSSDK_LATEST_NAME) + set(WINDOWSSDK_DIRS ${_win_sdk_dirs}) + + # Fallback, in case no preference found. + set(WINDOWSSDK_PREFERRED_DIR "${WINDOWSSDK_LATEST_DIR}") + set(WINDOWSSDK_PREFERRED_NAME "${WINDOWSSDK_LATEST_NAME}") + set(WINDOWSSDK_PREFERRED_FIRST_DIRS ${WINDOWSSDK_DIRS}) + set(WINDOWSSDK_FOUND_PREFERENCE OFF) +endif() + +# If we found indications of a user preference... +if(_win_sdk_preferred_sdk_dirs) + list(GET _win_sdk_preferred_sdk_dirs 0 WINDOWSSDK_PREFERRED_DIR) + windowssdk_name_lookup("${WINDOWSSDK_PREFERRED_DIR}" + WINDOWSSDK_PREFERRED_NAME) + set(WINDOWSSDK_PREFERRED_FIRST_DIRS + ${_win_sdk_preferred_sdk_dirs} + ${_win_sdk_dirs}) + list(REMOVE_DUPLICATES WINDOWSSDK_PREFERRED_FIRST_DIRS) + set(WINDOWSSDK_FOUND_PREFERENCE ON) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(WindowsSDK + "No compatible version of the Windows SDK or Platform SDK found." + WINDOWSSDK_DIRS) + +if(WINDOWSSDK_FOUND) + # Internal: Architecture-appropriate library directory names. + if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "ARM") + if(CMAKE_SIZEOF_VOID_P MATCHES "8") + # Only supported in Win10 SDK and up. + set(_winsdk_arch8 arm64) # what the WDK for Win8+ calls this architecture + else() + set(_winsdk_archbare /arm) # what the architecture used to be called in oldest SDKs + set(_winsdk_arch arm) # what the architecture used to be called + set(_winsdk_arch8 arm) # what the WDK for Win8+ calls this architecture + endif() + else() + if(CMAKE_SIZEOF_VOID_P MATCHES "8") + set(_winsdk_archbare /x64) # what the architecture used to be called in oldest SDKs + set(_winsdk_arch amd64) # what the architecture used to be called + set(_winsdk_arch8 x64) # what the WDK for Win8+ calls this architecture + else() + set(_winsdk_archbare ) # what the architecture used to be called in oldest SDKs + set(_winsdk_arch i386) # what the architecture used to be called + set(_winsdk_arch8 x86) # what the WDK for Win8+ calls this architecture + endif() + endif() + + function(get_windowssdk_from_component _component _var) + get_filename_component(_component "${_component}" ABSOLUTE) + file(TO_CMAKE_PATH "${_component}" _component) + foreach(_sdkdir ${WINDOWSSDK_DIRS}) + get_filename_component(_sdkdir "${_sdkdir}" ABSOLUTE) + string(LENGTH "${_sdkdir}" _sdklen) + file(RELATIVE_PATH _rel "${_sdkdir}" "${_component}") + # If we don't have any "parent directory" items... + if(NOT "${_rel}" MATCHES "[.][.]") + set(${_var} "${_sdkdir}" PARENT_SCOPE) + return() + endif() + endforeach() + # Fail. + set(${_var} "NOTFOUND" PARENT_SCOPE) + endfunction() + function(get_windowssdk_library_dirs _winsdk_dir _var) + set(_dirs) + set(_suffixes + "lib${_winsdk_archbare}" # SDKs like 7.1A + "lib/${_winsdk_arch}" # just because some SDKs have x86 dir and root dir + "lib/w2k/${_winsdk_arch}" # Win2k min requirement + "lib/wxp/${_winsdk_arch}" # WinXP min requirement + "lib/wnet/${_winsdk_arch}" # Win Server 2003 min requirement + "lib/wlh/${_winsdk_arch}" + "lib/wlh/um/${_winsdk_arch8}" # Win Vista ("Long Horn") min requirement + "lib/win7/${_winsdk_arch}" + "lib/win7/um/${_winsdk_arch8}" # Win 7 min requirement + ) + foreach(_ver + wlh # Win Vista ("Long Horn") min requirement + win7 # Win 7 min requirement + win8 # Win 8 min requirement + winv6.3 # Win 8.1 min requirement + ) + + list(APPEND _suffixes + "lib/${_ver}/${_winsdk_arch}" + "lib/${_ver}/um/${_winsdk_arch8}" + "lib/${_ver}/km/${_winsdk_arch8}" + ) + endforeach() + + # Look for WDF libraries in Win10+ SDK + foreach(_mode umdf kmdf) + file(GLOB _wdfdirs RELATIVE "${_winsdk_dir}" "${_winsdk_dir}/lib/wdf/${_mode}/${_winsdk_arch8}/*") + if(_wdfdirs) + list(APPEND _suffixes ${_wdfdirs}) + endif() + endforeach() + + # Look in each Win10+ SDK version for the components + foreach(_win10ver ${_winsdk_win10vers}) + foreach(_component um km ucrt mmos) + list(APPEND _suffixes "lib/${_win10ver}/${_component}/${_winsdk_arch8}") + endforeach() + endforeach() + + foreach(_suffix ${_suffixes}) + # Check to see if a library actually exists here. + file(GLOB _libs "${_winsdk_dir}/${_suffix}/*.lib") + if(_libs) + list(APPEND _dirs "${_winsdk_dir}/${_suffix}") + endif() + endforeach() + if("${_dirs}" STREQUAL "") + set(_dirs NOTFOUND) + else() + list(REMOVE_DUPLICATES _dirs) + endif() + set(${_var} ${_dirs} PARENT_SCOPE) + endfunction() + function(get_windowssdk_include_dirs _winsdk_dir _var) + set(_dirs) + + set(_subdirs shared um winrt km wdf mmos ucrt) + set(_suffixes Include) + + foreach(_dir ${_subdirs}) + list(APPEND _suffixes "Include/${_dir}") + endforeach() + + foreach(_ver ${_winsdk_win10vers}) + foreach(_dir ${_subdirs}) + list(APPEND _suffixes "Include/${_ver}/${_dir}") + endforeach() + endforeach() + + foreach(_suffix ${_suffixes}) + # Check to see if a header file actually exists here. + file(GLOB _headers "${_winsdk_dir}/${_suffix}/*.h") + if(_headers) + list(APPEND _dirs "${_winsdk_dir}/${_suffix}") + endif() + endforeach() + if("${_dirs}" STREQUAL "") + set(_dirs NOTFOUND) + else() + list(REMOVE_DUPLICATES _dirs) + endif() + set(${_var} ${_dirs} PARENT_SCOPE) + endfunction() + function(get_windowssdk_library_dirs_multiple _var) + set(_dirs) + foreach(_sdkdir ${ARGN}) + get_windowssdk_library_dirs("${_sdkdir}" _current_sdk_libdirs) + if(_current_sdk_libdirs) + list(APPEND _dirs ${_current_sdk_libdirs}) + endif() + endforeach() + if("${_dirs}" STREQUAL "") + set(_dirs NOTFOUND) + else() + list(REMOVE_DUPLICATES _dirs) + endif() + set(${_var} ${_dirs} PARENT_SCOPE) + endfunction() + function(get_windowssdk_include_dirs_multiple _var) + set(_dirs) + foreach(_sdkdir ${ARGN}) + get_windowssdk_include_dirs("${_sdkdir}" _current_sdk_incdirs) + if(_current_sdk_libdirs) + list(APPEND _dirs ${_current_sdk_incdirs}) + endif() + endforeach() + if("${_dirs}" STREQUAL "") + set(_dirs NOTFOUND) + else() + list(REMOVE_DUPLICATES _dirs) + endif() + set(${_var} ${_dirs} PARENT_SCOPE) + endfunction() +endif() diff --git a/cmake/nsis/hyperion-logo-vert.bmp b/cmake/nsis/hyperion-logo-vert.bmp new file mode 100644 index 0000000000000000000000000000000000000000..19a5bfa1d36e414f7bd76fc898544b22149c8516 GIT binary patch literal 154542 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 literal 0 HcmV?d00001 diff --git a/cmake/nsis/hyperion-logo.bmp b/cmake/nsis/hyperion-logo.bmp new file mode 100644 index 0000000000000000000000000000000000000000..7d6049644f6ad65e5bfc0377cd6a664193ea9fc4 GIT binary patch literal 88054 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 bj6wR*IdgC%6W8cYE`z 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 XEuahn*h=b$%~@w8sE+#oNE-hS2xjVK literal 0 HcmV?d00001 diff --git a/cmake/nsis/template/NSIS.InstallOptions.ini.in b/cmake/nsis/template/NSIS.InstallOptions.ini.in new file mode 100644 index 00000000..e40caa76 --- /dev/null +++ b/cmake/nsis/template/NSIS.InstallOptions.ini.in @@ -0,0 +1,46 @@ +[Settings] +NumFields=5 + +[Field 1] +Type=label +Text=By default @CPACK_PACKAGE_INSTALL_DIRECTORY@ does not add its directory to the system PATH. +Left=0 +Right=-1 +Top=0 +Bottom=20 + +[Field 2] +Type=radiobutton +Text=Do not add @CPACK_PACKAGE_NAME@ to the system PATH +Left=0 +Right=-1 +Top=30 +Bottom=40 +State=1 + +[Field 3] +Type=radiobutton +Text=Add @CPACK_PACKAGE_NAME@ to the system PATH for all users +Left=0 +Right=-1 +Top=40 +Bottom=50 +State=0 + +[Field 4] +Type=radiobutton +Text=Add @CPACK_PACKAGE_NAME@ to the system PATH for current user +Left=0 +Right=-1 +Top=50 +Bottom=60 +State=0 + +[Field 5] +Type=CheckBox +Text=Create @CPACK_PACKAGE_NAME@ Desktop Icon +Left=0 +Right=-1 +Top=80 +Bottom=90 +State=1 diff --git a/cmake/nsis/template/NSIS.template.in b/cmake/nsis/template/NSIS.template.in new file mode 100644 index 00000000..660bfa3f --- /dev/null +++ b/cmake/nsis/template/NSIS.template.in @@ -0,0 +1,978 @@ +; CPack install script designed for a nmake build + +;-------------------------------- +; You must define these values + + !define VERSION "@CPACK_PACKAGE_VERSION@" + !define PATCH "@CPACK_PACKAGE_VERSION_PATCH@" + !define INST_DIR "@CPACK_TEMPORARY_DIRECTORY@" + +;-------------------------------- +;Variables + + Var MUI_TEMP + Var STARTMENU_FOLDER + Var SV_ALLUSERS + Var START_MENU + Var DO_NOT_ADD_TO_PATH + Var ADD_TO_PATH_ALL_USERS + Var ADD_TO_PATH_CURRENT_USER + Var INSTALL_DESKTOP + Var IS_DEFAULT_INSTALLDIR +;-------------------------------- +;Include Modern UI + + !include "MUI.nsh" + + ;Default installation folder + InstallDir "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + +;-------------------------------- +;General + + ;Name and file + Name "@CPACK_NSIS_PACKAGE_NAME@" + OutFile "@CPACK_TOPLEVEL_DIRECTORY@/@CPACK_OUTPUT_FILE_NAME@" + + ;Set compression + SetCompressor @CPACK_NSIS_COMPRESSOR@ + + ;Require administrator access + RequestExecutionLevel admin + +@CPACK_NSIS_DEFINES@ + + !include Sections.nsh + +;--- Component support macros: --- +; The code for the add/remove functionality is from: +; http://nsis.sourceforge.net/Add/Remove_Functionality +; It has been modified slightly and extended to provide +; inter-component dependencies. +Var AR_SecFlags +Var AR_RegFlags +@CPACK_NSIS_SECTION_SELECTED_VARS@ + +; Loads the "selected" flag for the section named SecName into the +; variable VarName. +!macro LoadSectionSelectedIntoVar SecName VarName + SectionGetFlags ${${SecName}} $${VarName} + IntOp $${VarName} $${VarName} & ${SF_SELECTED} ;Turn off all other bits +!macroend + +; Loads the value of a variable... can we get around this? +!macro LoadVar VarName + IntOp $R0 0 + $${VarName} +!macroend + +; Sets the value of a variable +!macro StoreVar VarName IntValue + IntOp $${VarName} 0 + ${IntValue} +!macroend + +!macro InitSection SecName + ; This macro reads component installed flag from the registry and + ;changes checked state of the section on the components page. + ;Input: section index constant name specified in Section command. + + ClearErrors + ;Reading component status from registry + ReadRegDWORD $AR_RegFlags HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" "Installed" + IfErrors "default_${SecName}" + ;Status will stay default if registry value not found + ;(component was never installed) + IntOp $AR_RegFlags $AR_RegFlags & ${SF_SELECTED} ;Turn off all other bits + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading default section flags + IntOp $AR_SecFlags $AR_SecFlags & 0xFFFE ;Turn lowest (enabled) bit off + IntOp $AR_SecFlags $AR_RegFlags | $AR_SecFlags ;Change lowest bit + + ; Note whether this component was installed before + !insertmacro StoreVar ${SecName}_was_installed $AR_RegFlags + IntOp $R0 $AR_RegFlags & $AR_RegFlags + + ;Writing modified flags + SectionSetFlags ${${SecName}} $AR_SecFlags + + "default_${SecName}:" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected +!macroend + +!macro FinishSection SecName + ; This macro reads section flag set by user and removes the section + ;if it is not selected. + ;Then it writes component installed flag to registry + ;Input: section index constant name specified in Section command. + + SectionGetFlags ${${SecName}} $AR_SecFlags ;Reading section flags + ;Checking lowest bit: + IntOp $AR_SecFlags $AR_SecFlags & ${SF_SELECTED} + IntCmp $AR_SecFlags 1 "leave_${SecName}" + ;Section is not selected: + ;Calling Section uninstall macro and writing zero installed flag + !insertmacro "Remove_${${SecName}}" + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 0 + Goto "exit_${SecName}" + + "leave_${SecName}:" + ;Section is selected: + WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@\Components\${SecName}" \ + "Installed" 1 + + "exit_${SecName}:" +!macroend + +!macro RemoveSection_CPack SecName + ; This macro is used to call section's Remove_... macro + ;from the uninstaller. + ;Input: section index constant name specified in Section command. + + !insertmacro "Remove_${${SecName}}" +!macroend + +; Determine whether the selection of SecName changed +!macro MaybeSelectionChanged SecName + !insertmacro LoadVar ${SecName}_selected + SectionGetFlags ${${SecName}} $R1 + IntOp $R1 $R1 & ${SF_SELECTED} ;Turn off all other bits + + ; See if the status has changed: + IntCmp $R0 $R1 "${SecName}_unchanged" + !insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected + + IntCmp $R1 ${SF_SELECTED} "${SecName}_was_selected" + !insertmacro "Deselect_required_by_${SecName}" + goto "${SecName}_unchanged" + + "${SecName}_was_selected:" + !insertmacro "Select_${SecName}_depends" + + "${SecName}_unchanged:" +!macroend +;--- End of Add/Remove macros --- + +;-------------------------------- +;Interface Settings + + !define MUI_HEADERIMAGE + !define MUI_ABORTWARNING + +;---------------------------------------- +; based upon a script of "Written by KiCHiK 2003-01-18 05:57:02" +;---------------------------------------- +!verbose 3 +!include "WinMessages.NSH" +!verbose 4 +;==================================================== +; get_NT_environment +; Returns: the selected environment +; Output : head of the stack +;==================================================== +!macro select_NT_profile UN +Function ${UN}select_NT_profile + StrCmp $ADD_TO_PATH_ALL_USERS "1" 0 environment_single + DetailPrint "Selected environment for all users" + Push "all" + Return + environment_single: + DetailPrint "Selected environment for current user only." + Push "current" + Return +FunctionEnd +!macroend +!insertmacro select_NT_profile "" +!insertmacro select_NT_profile "un." +;---------------------------------------------------- +!define NT_current_env 'HKCU "Environment"' +!define NT_all_env 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + +!ifndef WriteEnvStr_RegKey + !ifdef ALL_USERS + !define WriteEnvStr_RegKey \ + 'HKLM "SYSTEM\CurrentControlSet\Control\Session Manager\Environment"' + !else + !define WriteEnvStr_RegKey 'HKCU "Environment"' + !endif +!endif + +; AddToPath - Adds the given dir to the search path. +; Input - head of the stack +; Note - Win9x systems requires reboot + +Function AddToPath + Exch $0 + Push $1 + Push $2 + Push $3 + + # don't add if the path doesn't exist + IfFileExists "$0\*.*" "" AddToPath_done + + ReadEnvStr $1 PATH + ; if the path is too long for a NSIS variable NSIS will return a 0 + ; length string. If we find that, then warn and skip any path + ; modification as it will trash the existing path. + StrLen $2 $1 + IntCmp $2 0 CheckPathLength_ShowPathWarning CheckPathLength_Done CheckPathLength_Done + CheckPathLength_ShowPathWarning: + Messagebox MB_OK|MB_ICONEXCLAMATION "Warning! PATH too long installer unable to modify PATH!" + Goto AddToPath_done + CheckPathLength_Done: + Push "$1;" + Push "$0;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$0\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + GetFullPathName /SHORT $3 $0 + Push "$1;" + Push "$3;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + Push "$1;" + Push "$3\;" + Call StrStr + Pop $2 + StrCmp $2 "" "" AddToPath_done + + Call IsNT + Pop $1 + StrCmp $1 1 AddToPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" a + FileSeek $1 -1 END + FileReadByte $1 $2 + IntCmp $2 26 0 +2 +2 # DOS EOF + FileSeek $1 -1 END # write over EOF + FileWrite $1 "$\r$\nSET PATH=%PATH%;$3$\r$\n" + FileClose $1 + SetRebootFlag true + Goto AddToPath_done + + AddToPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" ReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto DoTrim + ReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + DoTrim: + StrCmp $1 "" AddToPath_NTdoIt + Push $1 + Call Trim + Pop $1 + StrCpy $0 "$1;$0" + AddToPath_NTdoIt: + StrCmp $ADD_TO_PATH_ALL_USERS "1" WriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $0 + Goto DoSend + WriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $0 + DoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + AddToPath_done: + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + + +; RemoveFromPath - Remove a given dir from the path +; Input: head of the stack + +Function un.RemoveFromPath + Exch $0 + Push $1 + Push $2 + Push $3 + Push $4 + Push $5 + Push $6 + + IntFmt $6 "%c" 26 # DOS EOF + + Call un.IsNT + Pop $1 + StrCmp $1 1 unRemoveFromPath_NT + ; Not on NT + StrCpy $1 $WINDIR 2 + FileOpen $1 "$1\autoexec.bat" r + GetTempFileName $4 + FileOpen $2 $4 w + GetFullPathName /SHORT $0 $0 + StrCpy $0 "SET PATH=%PATH%;$0" + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoop: + FileRead $1 $3 + StrCpy $5 $3 1 -1 # read last char + StrCmp $5 $6 0 +2 # if DOS EOF + StrCpy $3 $3 -1 # remove DOS EOF so we can compare + StrCmp $3 "$0$\r$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0$\n" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "$0" unRemoveFromPath_dosLoopRemoveLine + StrCmp $3 "" unRemoveFromPath_dosLoopEnd + FileWrite $2 $3 + Goto unRemoveFromPath_dosLoop + unRemoveFromPath_dosLoopRemoveLine: + SetRebootFlag true + Goto unRemoveFromPath_dosLoop + + unRemoveFromPath_dosLoopEnd: + FileClose $2 + FileClose $1 + StrCpy $1 $WINDIR 2 + Delete "$1\autoexec.bat" + CopyFiles /SILENT $4 "$1\autoexec.bat" + Delete $4 + Goto unRemoveFromPath_done + + unRemoveFromPath_NT: + StrCmp $ADD_TO_PATH_ALL_USERS "1" unReadAllKey + ReadRegStr $1 ${NT_current_env} "PATH" + Goto unDoTrim + unReadAllKey: + ReadRegStr $1 ${NT_all_env} "PATH" + unDoTrim: + StrCpy $5 $1 1 -1 # copy last char + StrCmp $5 ";" +2 # if last char != ; + StrCpy $1 "$1;" # append ; + Push $1 + Push "$0;" + Call un.StrStr ; Find `$0;` in $1 + Pop $2 ; pos of our dir + StrCmp $2 "" unRemoveFromPath_done + ; else, it is in path + # $0 - path to add + # $1 - path var + StrLen $3 "$0;" + StrLen $4 $2 + StrCpy $5 $1 -$4 # $5 is now the part before the path to remove + StrCpy $6 $2 "" $3 # $6 is now the part after the path to remove + StrCpy $3 $5$6 + + StrCpy $5 $3 1 -1 # copy last char + StrCmp $5 ";" 0 +2 # if last char == ; + StrCpy $3 $3 -1 # remove last char + + StrCmp $ADD_TO_PATH_ALL_USERS "1" unWriteAllKey + WriteRegExpandStr ${NT_current_env} "PATH" $3 + Goto unDoSend + unWriteAllKey: + WriteRegExpandStr ${NT_all_env} "PATH" $3 + unDoSend: + SendMessage ${HWND_BROADCAST} ${WM_WININICHANGE} 0 "STR:Environment" /TIMEOUT=5000 + + unRemoveFromPath_done: + Pop $6 + Pop $5 + Pop $4 + Pop $3 + Pop $2 + Pop $1 + Pop $0 +FunctionEnd + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Uninstall sutff +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +########################################### +# Utility Functions # +########################################### + +;==================================================== +; IsNT - Returns 1 if the current system is NT, 0 +; otherwise. +; Output: head of the stack +;==================================================== +; IsNT +; no input +; output, top of the stack = 1 if NT or 0 if not +; +; Usage: +; Call IsNT +; Pop $R0 +; ($R0 at this point is 1 or 0) + +!macro IsNT un +Function ${un}IsNT + Push $0 + ReadRegStr $0 HKLM "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion + StrCmp $0 "" 0 IsNT_yes + ; we are not NT. + Pop $0 + Push 0 + Return + + IsNT_yes: + ; NT!!! + Pop $0 + Push 1 +FunctionEnd +!macroend +!insertmacro IsNT "" +!insertmacro IsNT "un." + +; StrStr +; input, top of stack = string to search for +; top of stack-1 = string to search in +; output, top of stack (replaces with the portion of the string remaining) +; modifies no other variables. +; +; Usage: +; Push "this is a long ass string" +; Push "ass" +; Call StrStr +; Pop $R0 +; ($R0 at this point is "ass string") + +!macro StrStr un +Function ${un}StrStr +Exch $R1 ; st=haystack,old$R1, $R1=needle + Exch ; st=old$R1,haystack + Exch $R2 ; st=old$R1,old$R2, $R2=haystack + Push $R3 + Push $R4 + Push $R5 + StrLen $R3 $R1 + StrCpy $R4 0 + ; $R1=needle + ; $R2=haystack + ; $R3=len(needle) + ; $R4=cnt + ; $R5=tmp + loop: + StrCpy $R5 $R2 $R3 $R4 + StrCmp $R5 $R1 done + StrCmp $R5 "" done + IntOp $R4 $R4 + 1 + Goto loop +done: + StrCpy $R1 $R2 "" $R4 + Pop $R5 + Pop $R4 + Pop $R3 + Pop $R2 + Exch $R1 +FunctionEnd +!macroend +!insertmacro StrStr "" +!insertmacro StrStr "un." + +Function Trim ; Added by Pelaca + Exch $R1 + Push $R2 +Loop: + StrCpy $R2 "$R1" 1 -1 + StrCmp "$R2" " " RTrim + StrCmp "$R2" "$\n" RTrim + StrCmp "$R2" "$\r" RTrim + StrCmp "$R2" ";" RTrim + GoTo Done +RTrim: + StrCpy $R1 "$R1" -1 + Goto Loop +Done: + Pop $R2 + Exch $R1 +FunctionEnd + +Function ConditionalAddToRegisty + Pop $0 + Pop $1 + StrCmp "$0" "" ConditionalAddToRegisty_EmptyString + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" \ + "$1" "$0" + ;MessageBox MB_OK "Set Registry: '$1' to '$0'" + DetailPrint "Set install registry entry: '$1' to '$0'" + ConditionalAddToRegisty_EmptyString: +FunctionEnd + +;-------------------------------- + +!ifdef CPACK_USES_DOWNLOAD +Function DownloadFile + IfFileExists $INSTDIR\* +2 + CreateDirectory $INSTDIR + Pop $0 + + ; Skip if already downloaded + IfFileExists $INSTDIR\$0 0 +2 + Return + + StrCpy $1 "@CPACK_DOWNLOAD_SITE@" + + try_again: + NSISdl::download "$1/$0" "$INSTDIR\$0" + + Pop $1 + StrCmp $1 "success" success + StrCmp $1 "Cancelled" cancel + MessageBox MB_OK "Download failed: $1" + cancel: + Return + success: +FunctionEnd +!endif + +;-------------------------------- +; Define some macro setting for the gui +@CPACK_NSIS_INSTALLER_MUI_ICON_CODE@ +@CPACK_NSIS_INSTALLER_ICON_CODE@ +@CPACK_NSIS_INSTALLER_MUI_WELCOMEFINISH_CODE@ +@CPACK_NSIS_INSTALLER_MUI_UNWELCOMEFINISH_CODE@ +@CPACK_NSIS_INSTALLER_MUI_COMPONENTS_DESC@ +@CPACK_NSIS_INSTALLER_MUI_FINISHPAGE_RUN_CODE@ + +;-------------------------------- +;Pages + @CPACK_NSIS_INSTALLER_WELCOME_TITLE_CODE@ + @CPACK_NSIS_INSTALLER_WELCOME_TITLE_3LINES_CODE@ + !insertmacro MUI_PAGE_WELCOME + + !insertmacro MUI_PAGE_LICENSE "@CPACK_RESOURCE_FILE_LICENSE@" + Page custom InstallOptionsPage + !insertmacro MUI_PAGE_DIRECTORY + + ;Start Menu Folder Page Configuration + !define MUI_STARTMENUPAGE_REGISTRY_ROOT "SHCTX" + !define MUI_STARTMENUPAGE_REGISTRY_KEY "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + !define MUI_STARTMENUPAGE_REGISTRY_VALUENAME "Start Menu Folder" + !insertmacro MUI_PAGE_STARTMENU Application $STARTMENU_FOLDER + + @CPACK_NSIS_PAGE_COMPONENTS@ + + !insertmacro MUI_PAGE_INSTFILES + @CPACK_NSIS_INSTALLER_FINISH_TITLE_CODE@ + @CPACK_NSIS_INSTALLER_FINISH_TITLE_3LINES_CODE@ + !insertmacro MUI_PAGE_FINISH + + !insertmacro MUI_UNPAGE_CONFIRM + !insertmacro MUI_UNPAGE_INSTFILES + +;-------------------------------- +;Languages + + !insertmacro MUI_LANGUAGE "English" ;first language is the default language + !insertmacro MUI_LANGUAGE "Albanian" + !insertmacro MUI_LANGUAGE "Arabic" + !insertmacro MUI_LANGUAGE "Basque" + !insertmacro MUI_LANGUAGE "Belarusian" + !insertmacro MUI_LANGUAGE "Bosnian" + !insertmacro MUI_LANGUAGE "Breton" + !insertmacro MUI_LANGUAGE "Bulgarian" + !insertmacro MUI_LANGUAGE "Croatian" + !insertmacro MUI_LANGUAGE "Czech" + !insertmacro MUI_LANGUAGE "Danish" + !insertmacro MUI_LANGUAGE "Dutch" + !insertmacro MUI_LANGUAGE "Estonian" + !insertmacro MUI_LANGUAGE "Farsi" + !insertmacro MUI_LANGUAGE "Finnish" + !insertmacro MUI_LANGUAGE "French" + !insertmacro MUI_LANGUAGE "German" + !insertmacro MUI_LANGUAGE "Greek" + !insertmacro MUI_LANGUAGE "Hebrew" + !insertmacro MUI_LANGUAGE "Hungarian" + !insertmacro MUI_LANGUAGE "Icelandic" + !insertmacro MUI_LANGUAGE "Indonesian" + !insertmacro MUI_LANGUAGE "Irish" + !insertmacro MUI_LANGUAGE "Italian" + !insertmacro MUI_LANGUAGE "Japanese" + !insertmacro MUI_LANGUAGE "Korean" + !insertmacro MUI_LANGUAGE "Kurdish" + !insertmacro MUI_LANGUAGE "Latvian" + !insertmacro MUI_LANGUAGE "Lithuanian" + !insertmacro MUI_LANGUAGE "Luxembourgish" + !insertmacro MUI_LANGUAGE "Macedonian" + !insertmacro MUI_LANGUAGE "Malay" + !insertmacro MUI_LANGUAGE "Mongolian" + !insertmacro MUI_LANGUAGE "Norwegian" + !insertmacro MUI_LANGUAGE "Polish" + !insertmacro MUI_LANGUAGE "Portuguese" + !insertmacro MUI_LANGUAGE "PortugueseBR" + !insertmacro MUI_LANGUAGE "Romanian" + !insertmacro MUI_LANGUAGE "Russian" + !insertmacro MUI_LANGUAGE "Serbian" + !insertmacro MUI_LANGUAGE "SerbianLatin" + !insertmacro MUI_LANGUAGE "SimpChinese" + !insertmacro MUI_LANGUAGE "Slovak" + !insertmacro MUI_LANGUAGE "Slovenian" + !insertmacro MUI_LANGUAGE "Spanish" + !insertmacro MUI_LANGUAGE "Swedish" + !insertmacro MUI_LANGUAGE "Thai" + !insertmacro MUI_LANGUAGE "TradChinese" + !insertmacro MUI_LANGUAGE "Turkish" + !insertmacro MUI_LANGUAGE "Ukrainian" + !insertmacro MUI_LANGUAGE "Welsh" + +;-------------------------------- +;Reserve Files + + ;These files should be inserted before other files in the data block + ;Keep these lines before any File command + ;Only for solid compression (by default, solid compression is enabled for BZIP2 and LZMA) + + ReserveFile "NSIS.InstallOptions.ini" + !insertmacro MUI_RESERVEFILE_INSTALLOPTIONS + + ; for UserInfo::GetName and UserInfo::GetAccountType + ReserveFile /plugin 'UserInfo.dll' + +;-------------------------------- +; Installation types +@CPACK_NSIS_INSTALLATION_TYPES@ + +;-------------------------------- +; Component sections +@CPACK_NSIS_COMPONENT_SECTIONS@ + +;-------------------------------- +;Installer Sections + +Section "-Core installation" + ;Use the entire tree produced by the INSTALL target. Keep the + ;list of directories here in sync with the RMDir commands below. + SetOutPath "$INSTDIR" + @CPACK_NSIS_EXTRA_PREINSTALL_COMMANDS@ + @CPACK_NSIS_FULL_INSTALL@ + + ;Store installation folder + WriteRegStr SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR + + ;Create uninstaller + WriteUninstaller "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + Push "DisplayName" + Push "@CPACK_NSIS_DISPLAY_NAME@" + Call ConditionalAddToRegisty + Push "DisplayVersion" + Push "@CPACK_PACKAGE_VERSION@" + Call ConditionalAddToRegisty + Push "Publisher" + Push "@CPACK_PACKAGE_VENDOR@" + Call ConditionalAddToRegisty + Push "UninstallString" + Push "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + Call ConditionalAddToRegisty + Push "NoRepair" + Push "1" + Call ConditionalAddToRegisty + + !ifdef CPACK_NSIS_ADD_REMOVE + ;Create add/remove functionality + Push "ModifyPath" + Push "$INSTDIR\AddRemove.exe" + Call ConditionalAddToRegisty + !else + Push "NoModify" + Push "1" + Call ConditionalAddToRegisty + !endif + + ; Optional registration + Push "DisplayIcon" + Push "$INSTDIR\@CPACK_NSIS_INSTALLED_ICON_NAME@" + Call ConditionalAddToRegisty + Push "HelpLink" + Push "@CPACK_NSIS_HELP_LINK@" + Call ConditionalAddToRegisty + Push "URLInfoAbout" + Push "@CPACK_NSIS_URL_INFO_ABOUT@" + Call ConditionalAddToRegisty + Push "Contact" + Push "@CPACK_NSIS_CONTACT@" + Call ConditionalAddToRegisty + !insertmacro MUI_INSTALLOPTIONS_READ $INSTALL_DESKTOP "NSIS.InstallOptions.ini" "Field 5" "State" + !insertmacro MUI_STARTMENU_WRITE_BEGIN Application + + ;Create shortcuts + CreateDirectory "$SMPROGRAMS\$STARTMENU_FOLDER" +@CPACK_NSIS_CREATE_ICONS@ +@CPACK_NSIS_CREATE_ICONS_EXTRA@ + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + + ;Read a value from an InstallOptions INI file + !insertmacro MUI_INSTALLOPTIONS_READ $DO_NOT_ADD_TO_PATH "NSIS.InstallOptions.ini" "Field 2" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_ALL_USERS "NSIS.InstallOptions.ini" "Field 3" "State" + !insertmacro MUI_INSTALLOPTIONS_READ $ADD_TO_PATH_CURRENT_USER "NSIS.InstallOptions.ini" "Field 4" "State" + + ; Write special uninstall registry entries + Push "StartMenu" + Push "$STARTMENU_FOLDER" + Call ConditionalAddToRegisty + Push "DoNotAddToPath" + Push "$DO_NOT_ADD_TO_PATH" + Call ConditionalAddToRegisty + Push "AddToPathAllUsers" + Push "$ADD_TO_PATH_ALL_USERS" + Call ConditionalAddToRegisty + Push "AddToPathCurrentUser" + Push "$ADD_TO_PATH_CURRENT_USER" + Call ConditionalAddToRegisty + Push "InstallToDesktop" + Push "$INSTALL_DESKTOP" + Call ConditionalAddToRegisty + + !insertmacro MUI_STARTMENU_WRITE_END + +@CPACK_NSIS_EXTRA_INSTALL_COMMANDS@ + +SectionEnd + +Section "-Add to path" + Push $INSTDIR\bin + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 doNotAddToPath + StrCmp $DO_NOT_ADD_TO_PATH "1" doNotAddToPath 0 + Call AddToPath + doNotAddToPath: +SectionEnd + +;-------------------------------- +; Create custom pages +Function InstallOptionsPage + !insertmacro MUI_HEADER_TEXT "Install Options" "Choose options for installing @CPACK_NSIS_PACKAGE_NAME@" + !insertmacro MUI_INSTALLOPTIONS_DISPLAY "NSIS.InstallOptions.ini" + +FunctionEnd + +;-------------------------------- +; determine admin versus local install +Function un.onInit + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + Goto done + StrCmp $1 "Power" 0 +3 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + Goto done + + noLM: + ;Get installation folder from registry if available + + done: + +FunctionEnd + +;--- Add/Remove callback functions: --- +!macro SectionList MacroName + ;This macro used to perform operation on multiple sections. + ;List all of your components in following manner here. +@CPACK_NSIS_COMPONENT_SECTION_LIST@ +!macroend + +Section -FinishComponents + ;Removes unselected components and writes component status to registry + !insertmacro SectionList "FinishSection" + +!ifdef CPACK_NSIS_ADD_REMOVE + ; Get the name of the installer executable + System::Call 'kernel32::GetModuleFileNameA(i 0, t .R0, i 1024) i r1' + StrCpy $R3 $R0 + + ; Strip off the last 13 characters, to see if we have AddRemove.exe + StrLen $R1 $R0 + IntOp $R1 $R0 - 13 + StrCpy $R2 $R0 13 $R1 + StrCmp $R2 "AddRemove.exe" addremove_installed + + ; We're not running AddRemove.exe, so install it + CopyFiles $R3 $INSTDIR\AddRemove.exe + + addremove_installed: +!endif +SectionEnd +;--- End of Add/Remove callback functions --- + +;-------------------------------- +; Component dependencies +Function .onSelChange + !insertmacro SectionList MaybeSelectionChanged +FunctionEnd + +;-------------------------------- +;Uninstaller Section + +Section "Uninstall" + ReadRegStr $START_MENU SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "StartMenu" + ;MessageBox MB_OK "Start menu is in: $START_MENU" + ReadRegStr $DO_NOT_ADD_TO_PATH SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "DoNotAddToPath" + ReadRegStr $ADD_TO_PATH_ALL_USERS SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathAllUsers" + ReadRegStr $ADD_TO_PATH_CURRENT_USER SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "AddToPathCurrentUser" + ;MessageBox MB_OK "Add to path: $DO_NOT_ADD_TO_PATH all users: $ADD_TO_PATH_ALL_USERS" + ReadRegStr $INSTALL_DESKTOP SHCTX \ + "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "InstallToDesktop" + ;MessageBox MB_OK "Install to desktop: $INSTALL_DESKTOP " + +@CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS@ + + ;Remove files we installed. + ;Keep the list of directories here in sync with the File commands above. +@CPACK_NSIS_DELETE_FILES@ +@CPACK_NSIS_DELETE_DIRECTORIES@ + +!ifdef CPACK_NSIS_ADD_REMOVE + ;Remove the add/remove program + Delete "$INSTDIR\AddRemove.exe" +!endif + + ;Remove the uninstaller itself. + Delete "$INSTDIR\@CPACK_NSIS_UNINSTALL_NAME@.exe" + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ;Remove the installation directory if it is empty. + RMDir "$INSTDIR" + + ; Remove the registry entries. + DeleteRegKey SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + ; Removes all optional components + !insertmacro SectionList "RemoveSection_CPack" + + !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP + + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS@ +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent directories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + startMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors startMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" startMenuDeleteLoopDone startMenuDeleteLoop + startMenuDeleteLoopDone: + + ; If the user changed the shortcut, then untinstall may not work. This should + ; try to fix it. + StrCpy $MUI_TEMP "$START_MENU" + Delete "$SMPROGRAMS\$MUI_TEMP\Uninstall.lnk" +@CPACK_NSIS_DELETE_ICONS_EXTRA@ + + ;Delete empty start menu parent directories + StrCpy $MUI_TEMP "$SMPROGRAMS\$MUI_TEMP" + + secondStartMenuDeleteLoop: + ClearErrors + RMDir $MUI_TEMP + GetFullPathName $MUI_TEMP "$MUI_TEMP\.." + + IfErrors secondStartMenuDeleteLoopDone + + StrCmp "$MUI_TEMP" "$SMPROGRAMS" secondStartMenuDeleteLoopDone secondStartMenuDeleteLoop + secondStartMenuDeleteLoopDone: + + DeleteRegKey /ifempty SHCTX "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" + + Push $INSTDIR\bin + StrCmp $DO_NOT_ADD_TO_PATH_ "1" doNotRemoveFromPath 0 + Call un.RemoveFromPath + doNotRemoveFromPath: +SectionEnd + +;-------------------------------- +; determine admin versus local install +; Is install for "AllUsers" or "JustMe"? +; Default to "JustMe" - set to "AllUsers" if admin or on Win9x +; This function is used for the very first "custom page" of the installer. +; This custom page does not show up visibly, but it executes prior to the +; first visible page and sets up $INSTDIR properly... +; Choose different default installation folder based on SV_ALLUSERS... +; "Program Files" for AllUsers, "My Documents" for JustMe... + +Function .onInit + StrCmp "@CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL@" "ON" 0 inst + + ReadRegStr $0 HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "UninstallString" + StrCmp $0 "" inst + + MessageBox MB_YESNOCANCEL|MB_ICONEXCLAMATION \ + "@CPACK_NSIS_PACKAGE_NAME@ is already installed. $\n$\nDo you want to uninstall the old version before installing the new one?" \ + /SD IDYES IDYES uninst IDNO inst + Abort + +;Run the uninstaller +uninst: + ClearErrors + StrLen $2 "\Uninstall.exe" + StrCpy $3 $0 -$2 # remove "\Uninstall.exe" from UninstallString to get path + ExecWait '"$0" /S _?=$3' ;Do not copy the uninstaller to a temp file + + IfErrors uninst_failed inst +uninst_failed: + MessageBox MB_OK|MB_ICONSTOP "Uninstall failed." + Abort + + +inst: + ; Reads components status for registry + !insertmacro SectionList "InitSection" + + ; check to see if /D has been used to change + ; the install directory by comparing it to the + ; install directory that is expected to be the + ; default + StrCpy $IS_DEFAULT_INSTALLDIR 0 + StrCmp "$INSTDIR" "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" 0 +2 + StrCpy $IS_DEFAULT_INSTALLDIR 1 + + StrCpy $SV_ALLUSERS "JustMe" + ; if default install dir then change the default + ; if it is installed for JustMe + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "$DOCUMENTS\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + ClearErrors + UserInfo::GetName + IfErrors noLM + Pop $0 + UserInfo::GetAccountType + Pop $1 + StrCmp $1 "Admin" 0 +4 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Admin group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + StrCmp $1 "Power" 0 +4 + SetShellVarContext all + ;MessageBox MB_OK 'User "$0" is in the Power Users group' + StrCpy $SV_ALLUSERS "AllUsers" + Goto done + + noLM: + StrCpy $SV_ALLUSERS "AllUsers" + ;Get installation folder from registry if available + + done: + StrCmp $SV_ALLUSERS "AllUsers" 0 +3 + StrCmp "$IS_DEFAULT_INSTALLDIR" "1" 0 +2 + StrCpy $INSTDIR "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@" + + StrCmp "@CPACK_NSIS_MODIFY_PATH@" "ON" 0 noOptionsPage + !insertmacro MUI_INSTALLOPTIONS_EXTRACT "NSIS.InstallOptions.ini" + + noOptionsPage: +FunctionEnd diff --git a/cmake/packages.cmake b/cmake/packages.cmake index 1aabfb84..beaed342 100644 --- a/cmake/packages.cmake +++ b/cmake/packages.cmake @@ -6,7 +6,7 @@ IF (APPLE) ELSEIF (UNIX) SET ( CPACK_GENERATOR "TGZ") ELSEIF (WIN32) - SET ( CPACK_GENERATOR "ZIP") + SET ( CPACK_GENERATOR "ZIP" "NSIS") ENDIF() # Determine packages by found generator executables @@ -21,32 +21,42 @@ IF(DEB_BUILDER_FOUND) SET ( CPACK_GENERATOR ${CPACK_GENERATOR} "DEB") ENDIF() +# Overwrite CPACK_SYSTEM_NAME for mac (visual) +if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") + if(${CMAKE_HOST_APPLE}) + set(CMAKE_SYSTEM_NAME "macOS") + endif() +endif() + # Apply to all packages, some of these can be overwritten with generator specific content # https://cmake.org/cmake/help/v3.5/module/CPack.html 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" ) - -IF ( NOT DEFINED DOCKER_PLATFORM ) - SET ( CPACK_PACKAGE_FILE_NAME "Hyperion-${HYPERION_VERSION}-${CMAKE_SYSTEM_NAME}") -ELSE() - SET ( CPACK_PACKAGE_FILE_NAME "Hyperion-${HYPERION_VERSION}-${CMAKE_SYSTEM_NAME}-${DOCKER_PLATFORM}") -ENDIF() +SET ( CPACK_PACKAGE_FILE_NAME "Hyperion-${HYPERION_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}") SET ( CPACK_PACKAGE_CONTACT "packages@hyperion-project.org") +SET ( CPACK_PACKAGE_VENDOR "hyperion-project") SET ( CPACK_PACKAGE_EXECUTABLES "hyperiond;Hyperion" ) +SET ( CPACK_PACKAGE_INSTALL_DIRECTORY "Hyperion" ) SET ( CPACK_PACKAGE_ICON "${CMAKE_SOURCE_DIR}/resources/icons/hyperion-icon-32px.png") + SET ( CPACK_PACKAGE_VERSION_MAJOR "${HYPERION_VERSION_MAJOR}") SET ( CPACK_PACKAGE_VERSION_MINOR "${HYPERION_VERSION_MINOR}") SET ( CPACK_PACKAGE_VERSION_PATCH "${HYPERION_VERSION_PATCH}") SET ( CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE" ) +SET ( CPACK_PACKAGE_EXECUTABLES "hyperiond;Hyperion" ) SET ( CPACK_CREATE_DESKTOP_LINKS "hyperiond;Hyperion" ) +# Define the install prefix path for cpack +IF ( UNIX ) + #SET ( CPACK_PACKAGING_INSTALL_PREFIX "share/hyperion") +ENDIF() + # Specific CPack Package Generators # https://cmake.org/Wiki/CMake:CPackPackageGenerators # .deb files for apt - SET ( CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA "${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/preinst;${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/postinst;${CMAKE_CURRENT_SOURCE_DIR}/cmake/debian/prerm" ) SET ( CPACK_DEBIAN_PACKAGE_SECTION "Miscellaneous" ) @@ -55,8 +65,6 @@ SET ( CPACK_DEBIAN_PACKAGE_SECTION "Miscellaneous" ) SET ( CPACK_RPM_PACKAGE_RELEASE 1) SET ( CPACK_RPM_PACKAGE_LICENSE "MIT") SET ( CPACK_RPM_PACKAGE_GROUP "Applications") - -# Notes: This is a dependency list for Fedora 27, different .rpm OSes use different names for their deps SET ( CPACK_RPM_PRE_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/rpm/preinst" ) SET ( CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/rpm/postinst" ) SET ( CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/rpm/prerm" ) @@ -68,29 +76,162 @@ SET ( CPACK_BUNDLE_ICON ${CMAKE_CURRENT_SOURCE_DIR}/cmake/osxbundle/Hyperion.icn SET ( CPACK_BUNDLE_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/cmake/osxbundle/Info.plist ) SET ( CPACK_BUNDLE_STARTUP_COMMAND "${CMAKE_SOURCE_DIR}/cmake/osxbundle/launch.sh" ) -# NSIS for windows, requires NSIS TODO finish -SET ( CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cmake/nsis/installer.ico") -SET ( CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/cmake/nsis/uninstaller.ico") -#SET ( CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/cmake/nsis/installer.bmp") #bmp required? If so, wrap in WIN32 check else: Use default icon instead -SET ( CPACK_NSIS_MODIFY_PATH ON) -SET ( CPACK_NSIS_DISPLAY_NAME "Hyperion Installer") -SET ( CPACK_NSIS_INSTALLED_ICON_NAME "Link to .exe") +# Windows NSIS +# Use custom script based on cpack nsis template +set(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/nsis/template ${CMAKE_MODULE_PATH}) +# Some path transformations +if(WIN32) + file(TO_NATIVE_PATH ${CPACK_PACKAGE_ICON} CPACK_PACKAGE_ICON) + STRING(REGEX REPLACE "\\\\" "\\\\\\\\" CPACK_PACKAGE_ICON ${CPACK_PACKAGE_ICON}) +endif() +file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/nsis/installer.ico" NSIS_HYP_ICO) +file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/nsis/hyperion-logo.bmp" NSIS_HYP_LOGO_HORI) +file(TO_NATIVE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/nsis/hyperion-logo-vert.bmp" NSIS_HYP_LOGO_VERT) +STRING(REGEX REPLACE "\\\\" "\\\\\\\\" NSIS_HYP_ICO "${NSIS_HYP_ICO}") +STRING(REGEX REPLACE "\\\\" "\\\\\\\\" NSIS_HYP_LOGO_VERT "${NSIS_HYP_LOGO_VERT}") +STRING(REGEX REPLACE "\\\\" "\\\\\\\\" NSIS_HYP_LOGO_HORI "${NSIS_HYP_LOGO_HORI}") + +SET ( CPACK_NSIS_MODIFY_PATH ON ) +SET ( CPACK_NSIS_MUI_ICON ${NSIS_HYP_ICO}) +SET ( CPACK_NSIS_MUI_UNIICON ${NSIS_HYP_ICO}) +SET ( CPACK_NSIS_MUI_HEADERIMAGE ${NSIS_HYP_LOGO_HORI} ) +SET ( CPACK_NSIS_MUI_WELCOMEFINISHPAGE_BITMAP ${NSIS_HYP_LOGO_VERT}) +SET ( CPACK_NSIS_DISPLAY_NAME "Hyperion Ambient Light") +SET ( CPACK_NSIS_PACKAGE_NAME "Hyperion" ) +SET ( CPACK_NSIS_INSTALLED_ICON_NAME "bin\\\\hyperiond.exe") SET ( CPACK_NSIS_HELP_LINK "https://www.hyperion-project.org") SET ( CPACK_NSIS_URL_INFO_ABOUT "https://www.hyperion-project.org") +# hyperiond startmenu link +#SET ( CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Hyperion.lnk' '$INSTDIR\\\\bin\\\\hyperiond.exe'") +#SET ( CPACK_NSIS_DELETE_ICONS_EXTRA "Delete '$SMPROGRAMS\\\\$START_MENU\\\\Hyperion.lnk'") +# hyperiond desktop link +#SET ( CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$DESKTOP\\\\Hyperion.lnk' '$INSTDIR\\\\bin\\\\hyperiond.exe' ") +#SET ( CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "Delete '$DESKTOP\\\\Hyperion.lnk' ") + +# With cli args: SET ( CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Hyperion (Debug).lnk' '$INSTDIR\\\\bin\\\\hyperiond.exe' '-d'") +#SET ( CPACK_NSIS_EXTRA_INSTALL_COMMANDS "CreateShortCut \\\"$DESKTOP\\\\Hyperion.lnk\\\" \\\"$INSTDIR\\\\bin\\\\hyperiond.exe\\\" ") +#SET ( CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS "Delete \\\"$DESKTOP\\\\Hyperion.lnk\\\" ") # define the install components -SET ( CPACK_COMPONENTS_ALL "${PLATFORM}" ) +# See also https://gitlab.kitware.com/cmake/community/-/wikis/doc/cpack/Component-Install-With-CPack +# and https://cmake.org/cmake/help/latest/module/CPackComponent.html +SET ( CPACK_COMPONENTS_GROUPING "ALL_COMPONENTS_IN_ONE") +# Components base +SET ( CPACK_COMPONENTS_ALL "Hyperion" "hyperion_remote" ) +# optional compiled +if(ENABLE_QT) + SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_qt" ) +endif() +if(ENABLE_AMLOGIC) + SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_aml" ) +endif() +if(ENABLE_V4L2) + SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_v4l2" ) +endif() +if(ENABLE_X11) + SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_x11" ) +endif() +if(ENABLE_DISPMANX) + SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_dispmanx" ) +endif() +if(ENABLE_FB) + SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_framebuffer" ) +endif() +if(ENABLE_OSX) + SET ( CPACK_COMPONENTS_ALL ${CPACK_COMPONENTS_ALL} "hyperion_osx" ) +endif() -SET ( CPACK_COMPONENT_${PLATFORM}_ARCHIVE_FILE "${CPACK_PACKAGE_FILE_NAME}" ) SET ( CPACK_ARCHIVE_COMPONENT_INSTALL ON ) - -SET ( CPACK_DEBIAN_${PLATFORM}_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}.deb" ) SET ( CPACK_DEB_COMPONENT_INSTALL ON ) - -SET ( CPACK_RPM_${PLATFORM}_FILE_NAME "${CPACK_PACKAGE_FILE_NAME}.rpm" ) SET ( CPACK_RPM_COMPONENT_INSTALL ON ) SET ( CPACK_STRIP_FILES ON ) # no code after following line! INCLUDE ( CPack ) + +cpack_add_install_type(Full DISPLAY_NAME "Full") +cpack_add_install_type(Min DISPLAY_NAME "Minimal") +cpack_add_component_group(Runtime EXPANDED DESCRIPTION "Hyperion runtime and hyperion-remote commandline tool") +cpack_add_component_group(Screencapture EXPANDED DESCRIPTION "Standalone Screencapture commandline programs") +# Components base +cpack_add_component(Hyperion + DISPLAY_NAME "Hyperion" + DESCRIPTION "Hyperion runtime" + INSTALL_TYPES Full Min + GROUP Runtime + REQUIRED +) +cpack_add_component(hyperion_remote + DISPLAY_NAME "Hyperion Remote" + DESCRIPTION "Hyperion remote cli tool" + INSTALL_TYPES Full + GROUP Runtime + DEPENDS Hyperion +) + +# optional compiled +if(ENABLE_QT) + cpack_add_component(hyperion_qt + DISPLAY_NAME "Qt Standalone Screencap" + DESCRIPTION "Qt based standalone screen capture" + INSTALL_TYPES Full + GROUP Screencapture + DEPENDS Hyperion + ) +endif() +if(ENABLE_AMLOGIC) + cpack_add_component(hyperion_aml + DISPLAY_NAME "Amlogic Standalone Screencap" + DESCRIPTION "Amlogic based standalone screen capture" + INSTALL_TYPES Full + GROUP Screencapture + DEPENDS Hyperion + ) +endif() +if(ENABLE_V4L2) + cpack_add_component(hyperion_v4l2 + DISPLAY_NAME "V4l2 Standalone Screencap" + DESCRIPTION "Video for Linux 2 based standalone screen capture" + INSTALL_TYPES Full + GROUP Screencapture + DEPENDS Hyperion + ) +endif() +if(ENABLE_X11) + cpack_add_component(hyperion_x11 + DISPLAY_NAME "X11 Standalone Screencap" + DESCRIPTION "X11 based standalone screen capture" + INSTALL_TYPES Full + GROUP Screencapture + DEPENDS Hyperion + ) +endif() +if(ENABLE_DISPMANX) + cpack_add_component(hyperion_dispmanx + DISPLAY_NAME "RPi dispmanx Standalone Screencap" + DESCRIPTION "Raspbery Pi dispmanx based standalone screen capture" + INSTALL_TYPES Full + GROUP Screencapture + DEPENDS Hyperion + ) +endif() +if(ENABLE_FB) + cpack_add_component(hyperion_framebuffer + DISPLAY_NAME "Framebuffer Standalone Screencap" + DESCRIPTION "Framebuffer based standalone screen capture" + INSTALL_TYPES Full + GROUP Screencapture + DEPENDS Hyperion + ) +endif() +if(ENABLE_OSX) + cpack_add_component(hyperion_osx + DISPLAY_NAME "Mac osx Standalone Screencap" + DESCRIPTION "Mac osx based standalone screen capture" + INSTALL_TYPES Full + GROUP Screencapture + DEPENDS Hyperion + ) +endif() + diff --git a/dependencies/CMakeLists.txt b/dependencies/CMakeLists.txt index 4c4094e8..2f143032 100644 --- a/dependencies/CMakeLists.txt +++ b/dependencies/CMakeLists.txt @@ -1,5 +1,8 @@ add_subdirectory(build/hidapi) -add_subdirectory(build/tinkerforge) + +if ( ENABLE_TINKERFORGE ) + add_subdirectory(build/tinkerforge) +endif() if(ENABLE_WS281XPWM) add_library(ws281x @@ -58,6 +61,7 @@ message(STATUS "Using flatbuffers compiler: " ${FLATBUFFERS_FLATC_EXECUTABLE}) function(compile_flattbuffer_schema SRC_FBS OUTPUT_DIR) string(REGEX REPLACE "\\.fbs$" "_generated.h" GEN_HEADER ${SRC_FBS}) + set_property(SOURCE ${GEN_HEADER} PROPERTY SKIP_AUTOMOC ON) if (ENABLE_AMLOGIC) add_custom_command( OUTPUT ${GEN_HEADER} @@ -181,6 +185,8 @@ function(PROTOBUF_GENERATE_CPP SRCS HDRS) COMMENT "Running C++ protocol buffer compiler on ${FIL}" VERBATIM ) + set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.cc" PROPERTY SKIP_AUTOMOC ON) + set_property(SOURCE "${CMAKE_CURRENT_BINARY_DIR}/${FIL_WE}.pb.h" PROPERTY SKIP_AUTOMOC ON) endforeach() set_source_files_properties(${${SRCS}} ${${HDRS}} PROPERTIES GENERATED TRUE) diff --git a/docs/docs/en/user/Installation.md b/docs/docs/en/user/Installation.md index 02f275a0..f09a45c0 100644 --- a/docs/docs/en/user/Installation.md +++ b/docs/docs/en/user/Installation.md @@ -1,18 +1,17 @@ # Install Hyperion -Hyperion supports various platforms for installation, as package or as a portable .zip. +Hyperion supports various platforms for installation, as package or portable .zip. ## Requirements -### Supported Hardware/Software +### Supported Systems * Raspberry Pi (See also [HyperBian](/en/user/HyperBian)) * Debian 9 | Ubuntu 16.04 or higher * Mac OS - * OpenELEC, LibreELEC -**Please note that some arm devices have limited support in terms of direct capturing** +**Please note that some arm devices have limited support in terms of screen capturing** ### Supported Browsers -Hyperion will be configured and controlled trough a web configuration. Also for mobile browsers. +Hyperion will be configured and controlled trough a web interface. * Chrome 47+ * Firefox 43+ * Opera 34+ @@ -20,32 +19,31 @@ Hyperion will be configured and controlled trough a web configuration. Also for * Microsoft Edge 14+ ::: warning Internet Explorer -Intenet Explorer is not supported +Internet Explorer is not supported ::: ## Install Hyperion * Raspberry Pi you can use [HyperBian](/en/user/HyperBian.md) for a fresh start. Or use the install system * We provide installation packages (.deb) to install Hyperion with a single click on Debian/Ubuntu based systems. - * A script installation for read-only systems like OpenELEC, LibreELEC. * Mac OSX - currently just a zip file with the binary ### Debian/Ubuntu For Debian/Ubuntu we provide a .deb file. A one click installation package that does the job for you. \ -Download the file here: \ +Download the file from the [Release page](https://github.com/hyperion-project/hyperion.ng/releases) \ Install from commandline by typing. \ -`sudo apt install ./Hyperion-2.0.0-Linux-x86_64-x11.deb` \ +`sudo apt install ./Hyperion-2.0.0-Linux-x86_64.deb` \ Hyperion can be now started from your start menu. ### Fedora For Fedora we provide a .rpm file. A one click installation package that does the job for you. \ -Download the file here: \ +Download the file from the [Release page](https://github.com/hyperion-project/hyperion.ng/releases) \ Install from commandline by typing. \ -`sudo dnf install ./Hyperion-2.0.0-Linux-x86_64-x11.rpm` \ +`sudo dnf install ./Hyperion-2.0.0-Linux-x86_64.rpm` \ Hyperion can be now started from your start menu. ## Uninstall Hyperion On Debian/Ubuntu you can remove Hyperion with this command \ -`sudo apt remove hyperion*` \ +`sudo apt remove hyperion` \ ### Hyperion user data -Hyperion stores user data inside your home directory (folder `.hyperion`). \ No newline at end of file +Hyperion stores user data inside your home directory (folder `.hyperion`). diff --git a/include/api/JsonCB.h b/include/api/JsonCB.h index 3c4c7ff5..95229bce 100644 --- a/include/api/JsonCB.h +++ b/include/api/JsonCB.h @@ -7,7 +7,9 @@ // components def #include // bonjour +#ifdef ENABLE_AVAHI #include +#endif // videModes #include // settings @@ -69,13 +71,13 @@ private slots: /// @brief handle component state changes /// void handleComponentState(const hyperion::Components comp, const bool state); - +#ifdef ENABLE_AVAHI /// /// @brief handle emits from bonjour wrapper /// @param bRegisters The full register map /// void handleBonjourChange(const QMap& bRegisters); - +#endif /// /// @brief handle emits from PriorityMuxer /// @@ -131,8 +133,10 @@ private: Hyperion* _hyperion; /// pointer of comp register ComponentRegister* _componentRegister; +#ifdef ENABLE_AVAHI /// Bonjour instance BonjourBrowserWrapper* _bonjour; +#endif /// priority muxer instance PriorityMuxer* _prioMuxer; /// contains all available commands diff --git a/include/utils/Image.h b/include/utils/Image.h index 0c5c8456..de1e7f30 100644 --- a/include/utils/Image.h +++ b/include/utils/Image.h @@ -8,6 +8,11 @@ #include #include +// https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types#ssize-t +#if defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif template class Image diff --git a/include/utils/Logger.h b/include/utils/Logger.h index 237ff6f8..5b8f9a2d 100644 --- a/include/utils/Logger.h +++ b/include/utils/Logger.h @@ -16,13 +16,13 @@ #define Debug(logger, ...) (logger)->Message(Logger::DEBUG , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) #define Info(logger, ...) (logger)->Message(Logger::INFO , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) #define Warning(logger, ...) (logger)->Message(Logger::WARNING, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) -#define Error(logger, ...) (logger)->Message(Logger::ERROR , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) +#define Error(logger, ...) (logger)->Message(Logger::ERRORR , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) // conditional log messages #define DebugIf(condition, logger, ...) if (condition) (logger)->Message(Logger::DEBUG , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) #define InfoIf(condition, logger, ...) if (condition) (logger)->Message(Logger::INFO , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) #define WarningIf(condition, logger, ...) if (condition) (logger)->Message(Logger::WARNING , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) -#define ErrorIf(condition, logger, ...) if (condition) (logger)->Message(Logger::ERROR , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) +#define ErrorIf(condition, logger, ...) if (condition) (logger)->Message(Logger::ERRORR , __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) // ================================================================ @@ -31,7 +31,14 @@ class Logger : public QObject Q_OBJECT public: - enum LogLevel { UNSET=0,DEBUG=1, INFO=2,WARNING=3,ERROR=4,OFF=5 }; + enum LogLevel { + UNSET, + DEBUG, + INFO, + WARNING, + ERRORR, + OFF + }; typedef struct { diff --git a/include/utils/SysInfo.h b/include/utils/SysInfo.h index 4ab17732..a4693fc9 100644 --- a/include/utils/SysInfo.h +++ b/include/utils/SysInfo.h @@ -2,7 +2,6 @@ #include #include -#include class SysInfo : public QObject { @@ -30,27 +29,4 @@ private: static SysInfo* _instance; HyperionSysInfo _sysinfo; - - struct QUnixOSVersion - { - QString productType; - QString productVersion; - QString prettyName; - }; - - QString machineHostName(); - QString currentCpuArchitecture(); - QString kernelType(); - QString kernelVersion(); - bool findUnixOsVersion(QUnixOSVersion &v); - - QByteArray getEtcFileFirstLine(const char *fileName); - bool readEtcRedHatRelease(QUnixOSVersion &v); - bool readEtcDebianVersion(QUnixOSVersion &v); - - bool readEtcOsRelease(SysInfo::QUnixOSVersion &v); - bool readEtcFile(SysInfo::QUnixOSVersion &v, const char *filename, const QByteArray &idKey, const QByteArray &versionKey, const QByteArray &prettyNameKey); - QByteArray getEtcFileContent(const char *filename); - QString unquote(const char *begin, const char *end); - bool readEtcLsbRelease(SysInfo::QUnixOSVersion &v); }; diff --git a/libsrc/CMakeLists.txt b/libsrc/CMakeLists.txt index 3f769a5a..66724b89 100644 --- a/libsrc/CMakeLists.txt +++ b/libsrc/CMakeLists.txt @@ -9,7 +9,6 @@ add_subdirectory(blackborder) add_subdirectory(jsonserver) add_subdirectory(flatbufserver) add_subdirectory(protoserver) -add_subdirectory(bonjour) add_subdirectory(ssdp) add_subdirectory(boblightserver) add_subdirectory(leddevice) @@ -20,3 +19,7 @@ add_subdirectory(webserver) add_subdirectory(db) add_subdirectory(api) add_subdirectory(python) + +if(ENABLE_AVAHI) + add_subdirectory(bonjour) +endif() \ No newline at end of file diff --git a/libsrc/api/JsonAPI.cpp b/libsrc/api/JsonAPI.cpp index 679152c5..375791ef 100644 --- a/libsrc/api/JsonAPI.cpp +++ b/libsrc/api/JsonAPI.cpp @@ -13,6 +13,7 @@ #include #include #include +#include // hyperion includes #include @@ -26,7 +27,9 @@ #include // bonjour wrapper +#ifdef ENABLE_AVAHI #include +#endif // ledmapping int <> string transform methods #include @@ -469,9 +472,8 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString QJsonObject grabbers; QJsonArray availableGrabbers; - QJsonObject availableProperties; -#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) +#if defined(ENABLE_DISPMANX) || defined(ENABLE_V4L2) || defined(ENABLE_FB) || defined(ENABLE_AMLOGIC) || defined(ENABLE_OSX) || defined(ENABLE_X11) || defined(ENABLE_QT) // get available grabbers //grabbers["active"] = ????; @@ -535,7 +537,8 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString // add sessions QJsonArray sessions; - for (auto session : BonjourBrowserWrapper::getInstance()->getAllServices()) +#ifdef ENABLE_AVAHI + for (auto session: BonjourBrowserWrapper::getInstance()->getAllServices()) { if (session.port < 0) continue; @@ -549,7 +552,7 @@ void JsonAPI::handleServerInfoCommand(const QJsonObject &message, const QString sessions.append(item); } info["sessions"] = sessions; - +#endif // add instance info QJsonArray instanceInfo; for (const auto &entry : API::getAllInstanceData()) diff --git a/libsrc/api/JsonCB.cpp b/libsrc/api/JsonCB.cpp index e6749d33..47b01c32 100644 --- a/libsrc/api/JsonCB.cpp +++ b/libsrc/api/JsonCB.cpp @@ -10,8 +10,9 @@ #include // bonjour wrapper - +#ifdef ENABLE_AVAHI #include +#endif // priorityMuxer #include @@ -21,6 +22,7 @@ // qt #include +#include // Image to led map helper #include @@ -31,7 +33,9 @@ JsonCB::JsonCB(QObject* parent) : QObject(parent) , _hyperion(nullptr) , _componentRegister(nullptr) + #ifdef ENABLE_AVAHI , _bonjour(BonjourBrowserWrapper::getInstance()) + #endif , _prioMuxer(nullptr) { _availableCommands << "components-update" << "sessions-update" << "priorities-update" << "imageToLedMapping-update" @@ -58,10 +62,12 @@ bool JsonCB::subscribeFor(const QString& type, const bool & unsubscribe) if(type == "sessions-update") { +#ifdef ENABLE_AVAHI if(unsubscribe) disconnect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange); else connect(_bonjour, &BonjourBrowserWrapper::browserChange, this, &JsonCB::handleBonjourChange, Qt::UniqueConnection); +#endif } if(type == "priorities-update") @@ -188,7 +194,7 @@ void JsonCB::handleComponentState(const hyperion::Components comp, const bool st doCallback("components-update", QVariant(data)); } - +#ifdef ENABLE_AVAHI void JsonCB::handleBonjourChange(const QMap& bRegisters) { QJsonArray data; @@ -207,7 +213,7 @@ void JsonCB::handleBonjourChange(const QMap& bRegisters) doCallback("sessions-update", QVariant(data)); } - +#endif void JsonCB::handlePriorityUpdate() { QJsonObject data; diff --git a/libsrc/effectengine/CMakeLists.txt b/libsrc/effectengine/CMakeLists.txt index 24f1e059..5dcd607c 100644 --- a/libsrc/effectengine/CMakeLists.txt +++ b/libsrc/effectengine/CMakeLists.txt @@ -1,9 +1,16 @@ -set(Python_ADDITIONAL_VERSIONS 3.5) -find_package(PythonLibs 3.5 REQUIRED) +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + find_package(Python3 COMPONENTS Interpreter Development REQUIRED) +else() + find_package (PythonLibs ${PYTHON_VERSION_STRING} EXACT) # Maps PythonLibs to the PythonInterp version of the main cmake +endif() # Include the python directory. Also include the parent (which is for example /usr/include) # which may be required when it is not includes by the (cross-) compiler by default. -include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + include_directories(${Python3_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS}/..) +else() + include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) +endif() # Define the current source locations SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/effectengine) @@ -31,5 +38,10 @@ target_link_libraries(effectengine python Qt5::Core Qt5::Gui - ${PYTHON_LIBRARIES} ) + +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + target_link_libraries( effectengine ${Python3_LIBRARIES} ) +else() + target_link_libraries( effectengine ${PYTHON_LIBRARIES} ) +endif() diff --git a/libsrc/grabber/qt/QtGrabber.cpp b/libsrc/grabber/qt/QtGrabber.cpp index 6015ea07..1ab5abf0 100644 --- a/libsrc/grabber/qt/QtGrabber.cpp +++ b/libsrc/grabber/qt/QtGrabber.cpp @@ -100,8 +100,17 @@ int QtGrabber::grabFrame(Image & image) } 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(),(size_t) _width*_height*3); + QImage imageFrame = resizedPixmap.toImage().convertToFormat( QImage::Format_RGB888); + + for (int y=0; y #include -#include // QT includes #include diff --git a/libsrc/hyperion/LedString.cpp b/libsrc/hyperion/LedString.cpp index 894d6dd5..b43727fa 100644 --- a/libsrc/hyperion/LedString.cpp +++ b/libsrc/hyperion/LedString.cpp @@ -1,6 +1,5 @@ // STL includes #include -#include #include // hyperion includes diff --git a/libsrc/jsonserver/JsonServer.cpp b/libsrc/jsonserver/JsonServer.cpp index 4fb12771..4abae32b 100644 --- a/libsrc/jsonserver/JsonServer.cpp +++ b/libsrc/jsonserver/JsonServer.cpp @@ -6,7 +6,9 @@ #include "JsonClientConnection.h" // bonjour include +#ifdef ENABLE_AVAHI #include +#endif #include // qt includes @@ -49,7 +51,7 @@ void JsonServer::start() return; } Info(_log, "Started on port %d", _port); - +#ifdef ENABLE_AVAHI if(_serviceRegister == nullptr) { _serviceRegister = new BonjourServiceRegister(this); @@ -61,6 +63,7 @@ void JsonServer::start() _serviceRegister = new BonjourServiceRegister(this); _serviceRegister->registerService("_hyperiond-json._tcp", _port); } +#endif } void JsonServer::stop() diff --git a/libsrc/leddevice/CMakeLists.txt b/libsrc/leddevice/CMakeLists.txt index 107f9c58..f1e5c392 100755 --- a/libsrc/leddevice/CMakeLists.txt +++ b/libsrc/leddevice/CMakeLists.txt @@ -27,7 +27,7 @@ FILE ( GLOB Leddevice_SOURCES "${CURRENT_SOURCE_DIR}/dev_other/*.cpp" ) -if ( ENABLE_OSX ) +if ( ENABLE_OSX OR WIN32 ) list(REMOVE_ITEM Leddevice_SOURCES "${CURRENT_SOURCE_DIR}/dev_other/LedDevicePiBlaster.h") list(REMOVE_ITEM Leddevice_SOURCES "${CURRENT_SOURCE_DIR}/dev_other/LedDevicePiBlaster.cpp") endif() @@ -87,9 +87,13 @@ target_link_libraries(leddevice ${CMAKE_THREAD_LIBS_INIT} Qt5::Network Qt5::SerialPort - ssdp + ssdp ) +if(WIN32) + target_link_libraries(leddevice ws2_32) +endif() + if(ENABLE_TINKERFORGE) target_link_libraries(leddevice tinkerforge) endif() diff --git a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp index 23355369..0e43448b 100644 --- a/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceFadeCandy.cpp @@ -1,5 +1,11 @@ #include "LedDeviceFadeCandy.h" +// https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types#ssize-t +#if defined(_MSC_VER) +#include +typedef SSIZE_T ssize_t; +#endif + static const signed MAX_NUM_LEDS = 10000; // OPC can handle 21845 leds - in theory, fadecandy device should handle 10000 leds static const unsigned OPC_SET_PIXELS = 0; // OPC command codes static const unsigned OPC_SYS_EX = 255; // OPC command codes diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp b/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp index fe23311d..ce32138b 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.cpp @@ -1,4 +1,9 @@ +#ifdef _WIN32 +#include +#else #include +#endif + #include // hyperion local includes diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h b/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h index 813633bb..8f2f3da7 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h +++ b/libsrc/leddevice/dev_net/LedDeviceUdpArtNet.h @@ -20,6 +20,7 @@ const ushort ARTNET_DEFAULT_PORT = 6454; // http://stackoverflow.com/questions/16396013/artnet-packet-structure typedef union { +#pragma pack(push, 1) struct { char ID[8]; // "Art-Net" uint16_t OpCode; // See Doc. Table 1 - OpCodes eg. 0x5000 OpOutput / OpDmx @@ -30,7 +31,8 @@ typedef union uint8_t Net; // high universe (not used) uint16_t Length; // data length (2 - 512) uint8_t Data[ DMX_MAX ]; // universe data - } __attribute__((packed)); + }; +#pragma pack(pop) uint8_t raw[ 18 + DMX_MAX ]; diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp b/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp index 14a80c64..06f9cd54 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp +++ b/libsrc/leddevice/dev_net/LedDeviceUdpE131.cpp @@ -1,4 +1,9 @@ +#ifdef _WIN32 +#include +#else #include +#endif + #include // hyperion local includes diff --git a/libsrc/leddevice/dev_net/LedDeviceUdpE131.h b/libsrc/leddevice/dev_net/LedDeviceUdpE131.h index 26457fba..c155eec8 100644 --- a/libsrc/leddevice/dev_net/LedDeviceUdpE131.h +++ b/libsrc/leddevice/dev_net/LedDeviceUdpE131.h @@ -48,6 +48,7 @@ const ushort E131_DEFAULT_PORT = 5568; /* E1.31 Packet Structure */ typedef union { +#pragma pack(push, 1) struct { /* Root Layer */ @@ -76,7 +77,8 @@ typedef union uint16_t address_increment; uint16_t property_value_count; uint8_t property_values[513]; - } __attribute__((packed)); + }; +#pragma pack(pop) uint8_t raw[638]; } e131_packet_t; diff --git a/libsrc/leddevice/dev_net/ProviderUdp.cpp b/libsrc/leddevice/dev_net/ProviderUdp.cpp index af7104ff..dd4b5b97 100644 --- a/libsrc/leddevice/dev_net/ProviderUdp.cpp +++ b/libsrc/leddevice/dev_net/ProviderUdp.cpp @@ -6,7 +6,6 @@ #include // Linux includes #include -#include #include #include diff --git a/libsrc/leddevice/dev_serial/LedDeviceDMX.cpp b/libsrc/leddevice/dev_serial/LedDeviceDMX.cpp index ecc0d6aa..06dfced1 100644 --- a/libsrc/leddevice/dev_serial/LedDeviceDMX.cpp +++ b/libsrc/leddevice/dev_serial/LedDeviceDMX.cpp @@ -1,6 +1,8 @@ #include "LedDeviceDMX.h" #include +#ifndef _WIN32 #include +#endif LedDeviceDMX::LedDeviceDMX(const QJsonObject &deviceConfig) : ProviderRs232() @@ -80,9 +82,14 @@ int LedDeviceDMX::write(const std::vector &ledValues) } _rs232Port.setBreakEnabled(true); +// Note Windows: There is no concept of ns sleeptime, the closest possible is 1ms but requested is 0,000176ms +#ifndef _WIN32 nanosleep((const struct timespec[]){{0, 176000L}}, NULL); // 176 uSec break time +#endif _rs232Port.setBreakEnabled(false); +#ifndef _WIN32 nanosleep((const struct timespec[]){{0, 12000L}}, NULL); // 176 uSec make after break time +#endif #undef uberdebug #ifdef uberdebug diff --git a/libsrc/python/CMakeLists.txt b/libsrc/python/CMakeLists.txt index cbe8f641..e6261f6a 100644 --- a/libsrc/python/CMakeLists.txt +++ b/libsrc/python/CMakeLists.txt @@ -1,8 +1,14 @@ -find_package(PythonLibs 3.5 REQUIRED) - # Include the python directory. Also include the parent (which is for example /usr/include) # which may be required when it is not includes by the (cross-) compiler by default. -include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + include_directories(${Python3_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS}/..) + add_compile_definitions(PYTHON_VERSION_MAJOR_MINOR=${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}) +else() + find_package (PythonLibs ${PYTHON_VERSION_STRING} EXACT) # Maps PythonLibs to the PythonInterp version of the main cmake + include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) + add_definitions(-DPYTHON_VERSION_MAJOR_MINOR=${PYTHON_VERSION_MAJOR}${PYTHON_VERSION_MINOR}) +endif() # Define the current source locations SET(CURRENT_HEADER_DIR ${CMAKE_SOURCE_DIR}/include/python) @@ -17,5 +23,10 @@ add_library(python target_link_libraries(python effectengine hyperion-utils - ${PYTHON_LIBRARIES} ) + +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + target_link_libraries( python ${Python3_LIBRARIES} ) +else() + target_link_libraries( python ${PYTHON_LIBRARIES} ) +endif() diff --git a/libsrc/python/PythonInit.cpp b/libsrc/python/PythonInit.cpp index 7bad37ab..4438cd6d 100644 --- a/libsrc/python/PythonInit.cpp +++ b/libsrc/python/PythonInit.cpp @@ -15,19 +15,31 @@ // modules to init #include +#ifdef _WIN32 + #define STRINGIFY2(x) #x + #define STRINGIFY(x) STRINGIFY2(x) +#endif + PythonInit::PythonInit() { // register modules EffectModule::registerHyperionExtensionModule(); // set Python module path when exists - const wchar_t *pythonPath = Py_DecodeLocale((QDir::cleanPath(qApp->applicationDirPath() + "/../lib/python")).toLatin1().data(), nullptr); - if(QDir(QString::fromWCharArray(pythonPath)).exists()) + wchar_t *pythonPath = Py_DecodeLocale((QDir::cleanPath(qApp->applicationDirPath() + "/../lib/python")).toLatin1().data(), nullptr); + #ifdef _WIN32 + pythonPath = Py_DecodeLocale((QDir::cleanPath(qApp->applicationDirPath())).toLatin1().data(), nullptr); + pythonPath = wcscat(pythonPath, L"/python" STRINGIFY(PYTHON_VERSION_MAJOR_MINOR) ".zip"); + if(QFile(QString::fromWCharArray(pythonPath)).exists()) + #else + if(QDir(QString::fromWCharArray(pythonPath)).exists()) + #endif + { Py_NoSiteFlag++; Py_SetPath(pythonPath); + PyMem_RawFree(pythonPath); } - delete pythonPath; // init Python Debug(Logger::getInstance("DAEMON"), "Initializing Python interpreter"); diff --git a/libsrc/utils/Logger.cpp b/libsrc/utils/Logger.cpp index 5d1cf518..351a6c5b 100644 --- a/libsrc/utils/Logger.cpp +++ b/libsrc/utils/Logger.cpp @@ -3,13 +3,21 @@ #include #include -#include +#ifndef _WIN32 +#include +#elif _WIN32 +#include +#include +#pragma comment(lib, "Shlwapi.lib") +#endif #include #include static const char * LogLevelStrings[] = { "", "DEBUG", "INFO", "WARNING", "ERROR" }; +#ifndef _WIN32 static const int LogLevelSysLog[] = { LOG_DEBUG, LOG_DEBUG, LOG_INFO, LOG_WARNING, LOG_ERR }; +#endif static unsigned int loggerCount = 0; static unsigned int loggerId = 0; @@ -99,8 +107,19 @@ Logger::Logger ( QString name, LogLevel minLevel ) { #ifdef __GLIBC__ const char* _appname_char = program_invocation_short_name; -#else +#elif !defined(_WIN32) const char* _appname_char = getprogname(); +#else + char fileName[MAX_PATH]; + char *_appname_char; + HINSTANCE hinst = GetModuleHandle(NULL); + if (GetModuleFileNameA(hinst, fileName, sizeof(fileName))) + { + _appname_char = PathFindFileName(fileName); + *(PathFindExtension(fileName)) = 0; + } + else + _appname_char = "unknown"; #endif _appname = QString(_appname_char).toLower(); @@ -108,7 +127,9 @@ Logger::Logger ( QString name, LogLevel minLevel ) if (_syslogEnabled && loggerCount == 1 ) { + #ifndef _WIN32 openlog (_appname_char, LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL0); + #endif } } @@ -116,8 +137,10 @@ Logger::~Logger() { //Debug(this, "logger '%s' destroyed", QSTRING_CSTR(_name) ); loggerCount--; +#ifndef _WIN32 if ( loggerCount == 0 ) closelog(); +#endif } void Logger::Message(LogLevel level, const char* sourceFile, const char* func, unsigned int line, const char* fmt, ...) @@ -142,8 +165,10 @@ void Logger::Message(LogLevel level, const char* sourceFile, const char* func, u std::cout << QString("[" + repMsg.appName + " " + repMsg.loggerName + "] <" + LogLevelStrings[repMsg.level] + "> " + repMsg.message).toStdString() << std::endl; +#ifndef _WIN32 if ( _syslogEnabled && repMsg.level >= Logger::WARNING ) syslog (LogLevelSysLog[repMsg.level], "Previous line repeats %d times", _repeatCount); +#endif _repeatCount = 0; }; @@ -185,10 +210,10 @@ void Logger::Message(LogLevel level, const char* sourceFile, const char* func, u } std::cout << QString("[" + _appname + " " + _name + "] <" + LogLevelStrings[level] + "> " + location + msg).toStdString() << std::endl; - +#ifndef _WIN32 if ( _syslogEnabled && level >= Logger::WARNING ) syslog (LogLevelSysLog[level], "%s", msg); - +#endif _repeatMessage = logMsg; } } diff --git a/libsrc/utils/Process.cpp b/libsrc/utils/Process.cpp index e4b4b1b0..31155440 100644 --- a/libsrc/utils/Process.cpp +++ b/libsrc/utils/Process.cpp @@ -1,3 +1,19 @@ +#ifdef _WIN32 +#include +#include +#include +namespace Process { + +void restartHyperion(bool asNewProcess){} + +QByteArray command_exec(QString cmd, QByteArray data) +{ + return QSTRING_CSTR(QString()); +} +}; + +#else + #include #include @@ -55,3 +71,5 @@ QByteArray command_exec(QString cmd, QByteArray data) } }; + +#endif \ No newline at end of file diff --git a/libsrc/utils/RgbChannelAdjustment.cpp b/libsrc/utils/RgbChannelAdjustment.cpp index 84d6d059..b42616ea 100644 --- a/libsrc/utils/RgbChannelAdjustment.cpp +++ b/libsrc/utils/RgbChannelAdjustment.cpp @@ -58,9 +58,9 @@ void RgbChannelAdjustment::apply(uint8_t input, uint8_t brightness, uint8_t & re if (!_initialized[input]) { - _mapping[RED ][input] = qMin( ((_brightness * input * _adjust[RED ]) / 65025), UINT8_MAX); - _mapping[GREEN][input] = qMin( ((_brightness * input * _adjust[GREEN]) / 65025), UINT8_MAX); - _mapping[BLUE ][input] = qMin( ((_brightness * input * _adjust[BLUE ]) / 65025), UINT8_MAX); + _mapping[RED ][input] = qMin( ((_brightness * input * _adjust[RED ]) / 65025), (int)UINT8_MAX); + _mapping[GREEN][input] = qMin( ((_brightness * input * _adjust[GREEN]) / 65025), (int)UINT8_MAX); + _mapping[BLUE ][input] = qMin( ((_brightness * input * _adjust[BLUE ]) / 65025), (int)UINT8_MAX); _initialized[input] = true; } red = _mapping[RED ][input]; diff --git a/libsrc/utils/SysInfo.cpp b/libsrc/utils/SysInfo.cpp index dcdcb3ec..5b73b8ea 100644 --- a/libsrc/utils/SysInfo.cpp +++ b/libsrc/utils/SysInfo.cpp @@ -2,34 +2,21 @@ #include #include -#include -#include -#include "HyperionConfig.h" - -#include -#include -#include -#include -#include -#include SysInfo* SysInfo::_instance = nullptr; SysInfo::SysInfo() : QObject() { - SysInfo::QUnixOSVersion v; - findUnixOsVersion(v); - - _sysinfo.kernelType = kernelType(); - _sysinfo.kernelVersion = kernelVersion(); - _sysinfo.architecture = currentCpuArchitecture(); + _sysinfo.kernelType = QSysInfo::kernelType(); + _sysinfo.kernelVersion = QSysInfo::kernelVersion(); + _sysinfo.architecture = QSysInfo::currentCpuArchitecture(); _sysinfo.wordSize = QString::number(QSysInfo::WordSize); - _sysinfo.productType = v.productType; - _sysinfo.productVersion = v.productVersion; - _sysinfo.prettyName = v.prettyName; + _sysinfo.productType = QSysInfo::productType(); + _sysinfo.productVersion = QSysInfo::productVersion(); + _sysinfo.prettyName = QSysInfo::prettyProductName(); _sysinfo.hostName = QHostInfo::localHostName(); - _sysinfo.domainName = QHostInfo::localDomainName(); + _sysinfo.domainName = QHostInfo::localDomainName(); } SysInfo::~SysInfo() @@ -40,259 +27,6 @@ SysInfo::HyperionSysInfo SysInfo::get() { if ( SysInfo::_instance == nullptr ) SysInfo::_instance = new SysInfo(); - + return SysInfo::_instance->_sysinfo; } - - - -QString SysInfo::kernelType() -{ -#if defined(Q_OS_WIN) - return QStringLiteral("winnt"); -#elif defined(Q_OS_UNIX) - struct utsname u; - if (uname(&u) == 0) - return QString::fromLatin1(u.sysname).toLower(); -#endif - return QString(); -} - -QString SysInfo::kernelVersion() -{ - struct utsname u; - if (uname(&u) == 0) - return QString::fromLocal8Bit(u.release).toLower(); - - return QString(); -} - -QString SysInfo::machineHostName() -{ -#if defined(Q_OS_LINUX) - // gethostname(3) on Linux just calls uname(2), so do it ourselves and avoid a memcpy - struct utsname u; - if (uname(&u) == 0) - return QString::fromLocal8Bit(u.nodename); -#else - char hostName[512]; - if (gethostname(hostName, sizeof(hostName)) == -1) - return QString(); - hostName[sizeof(hostName) - 1] = '\0'; - return QString::fromLocal8Bit(hostName); -#endif - return QString(); -} - - -QString SysInfo::currentCpuArchitecture() -{ -#if defined(Q_OS_UNIX) - long ret = -1; - struct utsname u; - - if (ret == -1) - ret = uname(&u); - - // we could use detectUnixVersion() above, but we only need a field no other function does - if (ret != -1) - { - // the use of QT_BUILD_INTERNAL here is simply to ensure all branches build - // as we don't often build on some of the less common platforms -# if defined(Q_PROCESSOR_ARM) - if (strcmp(u.machine, "aarch64") == 0) - return QStringLiteral("arm64"); - if (strncmp(u.machine, "armv", 4) == 0) - return QStringLiteral("arm"); -# endif -# if defined(Q_PROCESSOR_POWER) - // harmonize "powerpc" and "ppc" to "power" - if (strncmp(u.machine, "ppc", 3) == 0) - return QLatin1String("power") + QLatin1String(u.machine + 3); - if (strncmp(u.machine, "powerpc", 7) == 0) - return QLatin1String("power") + QLatin1String(u.machine + 7); - if (strcmp(u.machine, "Power Macintosh") == 0) - return QLatin1String("power"); -# endif -# if defined(Q_PROCESSOR_X86) - // harmonize all "i?86" to "i386" - if (strlen(u.machine) == 4 && u.machine[0] == 'i' && u.machine[2] == '8' && u.machine[3] == '6') - return QStringLiteral("i386"); - if (strcmp(u.machine, "amd64") == 0) // Solaris - return QStringLiteral("x86_64"); -# endif - return QString::fromLatin1(u.machine); - } -#endif - return QString(); -} - -bool SysInfo::findUnixOsVersion(SysInfo::QUnixOSVersion &v) -{ - if (readEtcOsRelease(v)) - return true; - if (readEtcLsbRelease(v)) - return true; -#if defined(Q_OS_LINUX) - if (readEtcRedHatRelease(v)) - return true; - if (readEtcDebianVersion(v)) - return true; -#endif - return false; -} - - -QByteArray SysInfo::getEtcFileFirstLine(const char *fileName) -{ - QByteArray buffer = getEtcFileContent(fileName); - if (buffer.isEmpty()) - return QByteArray(); - - const char *ptr = buffer.constData(); - int eol = buffer.indexOf("\n"); - return QByteArray(ptr, eol).trimmed(); -} - -bool SysInfo::readEtcRedHatRelease(SysInfo::QUnixOSVersion &v) -{ - // /etc/redhat-release analysed should be a one line file - // the format of its content is - // i.e. "Red Hat Enterprise Linux Workstation release 6.5 (Santiago)" - QByteArray line = getEtcFileFirstLine("/etc/redhat-release"); - if (line.isEmpty()) - return false; - - v.prettyName = QString::fromLatin1(line); - - const char keyword[] = "release "; - int releaseIndex = line.indexOf(keyword); - v.productType = QString::fromLatin1(line.mid(0, releaseIndex)).remove(QLatin1Char(' ')); - int spaceIndex = line.indexOf(' ', releaseIndex + strlen(keyword)); - v.productVersion = QString::fromLatin1(line.mid(releaseIndex + strlen(keyword), - spaceIndex > -1 ? spaceIndex - releaseIndex - int(strlen(keyword)) : -1)); - return true; -} - -bool SysInfo::readEtcDebianVersion(SysInfo::QUnixOSVersion &v) -{ - // /etc/debian_version analysed should be a one line file - // the format of its content is - // i.e. "jessie/sid" - QByteArray line = getEtcFileFirstLine("/etc/debian_version"); - if (line.isEmpty()) - return false; - - v.productType = QStringLiteral("Debian"); - v.productVersion = QString::fromLatin1(line); - return true; -} - -QString SysInfo::unquote(const char *begin, const char *end) -{ - if (*begin == '"') { - Q_ASSERT(end[-1] == '"'); - return QString::fromLatin1(begin + 1, end - begin - 2); - } - return QString::fromLatin1(begin, end - begin); -} - -QByteArray SysInfo::getEtcFileContent(const char *filename) -{ - // we're avoiding QFile here - int fd = open(filename, O_RDONLY); - if (fd == -1) - return QByteArray(); - - struct stat sbuf; - if (::fstat(fd, &sbuf) == -1) { - close(fd); - return QByteArray(); - } - - QByteArray buffer(sbuf.st_size, Qt::Uninitialized); - buffer.resize(read(fd, buffer.data(), sbuf.st_size)); - close(fd); - return buffer; -} - -bool SysInfo::readEtcFile(SysInfo::QUnixOSVersion &v, const char *filename, - const QByteArray &idKey, const QByteArray &versionKey, const QByteArray &prettyNameKey) -{ - - QByteArray buffer = getEtcFileContent(filename); - if (buffer.isEmpty()) - return false; - - const char *ptr = buffer.constData(); - const char *end = buffer.constEnd(); - const char *eol; - QByteArray line; - for ( ; ptr != end; ptr = eol + 1) { - // find the end of the line after ptr - eol = static_cast(memchr(ptr, '\n', end - ptr)); - if (!eol) - eol = end - 1; - line.setRawData(ptr, eol - ptr); - - if (line.startsWith(idKey)) { - ptr += idKey.length(); - v.productType = unquote(ptr, eol); - continue; - } - - if (line.startsWith(prettyNameKey)) { - ptr += prettyNameKey.length(); - v.prettyName = unquote(ptr, eol); - continue; - } - - if (line.startsWith(versionKey)) { - ptr += versionKey.length(); - v.productVersion = unquote(ptr, eol); - continue; - } - } - - return true; -} - -bool SysInfo::readEtcOsRelease(SysInfo::QUnixOSVersion &v) -{ - return readEtcFile(v, "/etc/os-release", QByteArrayLiteral("ID="), - QByteArrayLiteral("VERSION_ID="), QByteArrayLiteral("PRETTY_NAME=")); -} - -bool SysInfo::readEtcLsbRelease(SysInfo::QUnixOSVersion &v) -{ - bool ok = readEtcFile(v, "/etc/lsb-release", QByteArrayLiteral("DISTRIB_ID="), - QByteArrayLiteral("DISTRIB_RELEASE="), QByteArrayLiteral("DISTRIB_DESCRIPTION=")); - if (ok && (v.prettyName.isEmpty() || v.prettyName == v.productType)) { - // some distributions have redundant information for the pretty name, - // so try /etc/-release - - // we're still avoiding QFile here - QByteArray distrorelease = "/etc/" + v.productType.toLatin1().toLower() + "-release"; - int fd = open(distrorelease, O_RDONLY); - if (fd != -1) { - struct stat sbuf; - if (::fstat(fd, &sbuf) != -1 && sbuf.st_size > v.prettyName.length()) { - // file apparently contains interesting information - QByteArray buffer(sbuf.st_size, Qt::Uninitialized); - buffer.resize(read(fd, buffer.data(), sbuf.st_size)); - v.prettyName = QString::fromLatin1(buffer.trimmed()); - } - close(fd); - } - } - - // some distributions have a /etc/lsb-release file that does not provide the values - // we are looking for, i.e. DISTRIB_ID, DISTRIB_RELEASE and DISTRIB_DESCRIPTION. - // Assuming that neither DISTRIB_ID nor DISTRIB_RELEASE were found, or contained valid values, - // returning false for readEtcLsbRelease will allow further /etc/-release parsing. - return ok && !(v.productType.isEmpty() && v.productVersion.isEmpty()); -} - - - - diff --git a/libsrc/webserver/WebServer.cpp b/libsrc/webserver/WebServer.cpp index 8c8b83c4..5440aed7 100644 --- a/libsrc/webserver/WebServer.cpp +++ b/libsrc/webserver/WebServer.cpp @@ -6,8 +6,9 @@ #include // bonjour +#ifdef ENABLE_AVAHI #include - +#endif // netUtil #include @@ -56,7 +57,7 @@ void WebServer::onServerStarted (quint16 port) _inited= true; Info(_log, "Started on port %d name '%s'", port ,_server->getServerName().toStdString().c_str()); - +#ifdef ENABLE_AVAHI if(_serviceRegister == nullptr) { _serviceRegister = new BonjourServiceRegister(this); @@ -68,6 +69,7 @@ void WebServer::onServerStarted (quint16 port) _serviceRegister = new BonjourServiceRegister(this); _serviceRegister->registerService("_hyperiond-http._tcp", port); } +#endif emit stateChange(true); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cac3a381..f6954f3c 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,6 +22,10 @@ if(ENABLE_FB) add_subdirectory(hyperion-framebuffer) endif() +if(ENABLE_QT) + add_subdirectory(hyperion-qt) +endif() + if(ENABLE_OSX) add_subdirectory(hyperion-osx) endif() diff --git a/src/hyperion-aml/CMakeLists.txt b/src/hyperion-aml/CMakeLists.txt index 571077c9..23e6d3b6 100644 --- a/src/hyperion-aml/CMakeLists.txt +++ b/src/hyperion-aml/CMakeLists.txt @@ -41,10 +41,10 @@ if (ENABLE_AMLOGIC) ) endif() -install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" ) +install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_aml" ) 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}" ) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_aml" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_aml" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_aml" ) endif(CMAKE_HOST_UNIX) diff --git a/src/hyperion-dispmanx/CMakeLists.txt b/src/hyperion-dispmanx/CMakeLists.txt index ad81ea16..e6c8e555 100644 --- a/src/hyperion-dispmanx/CMakeLists.txt +++ b/src/hyperion-dispmanx/CMakeLists.txt @@ -42,10 +42,10 @@ target_link_libraries( ${PROJECT_NAME} Qt5::Network ) -install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" ) +install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_dispmanx" ) 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}" ) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_dispmanx" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_dispmanx" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_dispmanx" ) endif(CMAKE_HOST_UNIX) diff --git a/src/hyperion-framebuffer/CMakeLists.txt b/src/hyperion-framebuffer/CMakeLists.txt index 1e82137d..d7a1517d 100644 --- a/src/hyperion-framebuffer/CMakeLists.txt +++ b/src/hyperion-framebuffer/CMakeLists.txt @@ -40,10 +40,10 @@ if (ENABLE_AMLOGIC) ) endif() -install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" ) +install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_framebuffer" ) 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}" ) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_framebuffer" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_framebuffer" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_framebuffer" ) endif(CMAKE_HOST_UNIX) diff --git a/src/hyperion-osx/CMakeLists.txt b/src/hyperion-osx/CMakeLists.txt index 706a429b..d4e3c626 100644 --- a/src/hyperion-osx/CMakeLists.txt +++ b/src/hyperion-osx/CMakeLists.txt @@ -34,10 +34,10 @@ target_link_libraries( ${PROJECT_NAME} Qt5::Network ) -install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" ) +install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_osx" ) 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}" ) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_osx" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_osx" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_osx" ) endif(CMAKE_HOST_UNIX) diff --git a/src/hyperion-qt/CMakeLists.txt b/src/hyperion-qt/CMakeLists.txt index f1189f29..b54837e1 100644 --- a/src/hyperion-qt/CMakeLists.txt +++ b/src/hyperion-qt/CMakeLists.txt @@ -20,6 +20,7 @@ set(Hyperion_QT_SOURCES add_executable(${PROJECT_NAME} ${Hyperion_QT_HEADERS} ${Hyperion_QT_SOURCES} + ${WIN_RC_ICON_FILE} ) target_link_libraries(${PROJECT_NAME} @@ -33,10 +34,14 @@ target_link_libraries(${PROJECT_NAME} Qt5::Network ) -install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" ) +if(NOT WIN32) + install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_qt" ) +else() + install ( TARGETS ${PROJECT_NAME} DESTINATION "bin" COMPONENT "hyperion_qt" ) +endif() 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}" ) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_qt" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_qt" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_qt" ) endif(CMAKE_HOST_UNIX) diff --git a/src/hyperion-remote/CMakeLists.txt b/src/hyperion-remote/CMakeLists.txt index 041b77cd..ea8d8c1c 100644 --- a/src/hyperion-remote/CMakeLists.txt +++ b/src/hyperion-remote/CMakeLists.txt @@ -3,7 +3,7 @@ project(hyperion-remote) find_package(Qt5 COMPONENTS Core Gui Widgets Network REQUIRED) -# The following I do not undrstand completely... +# The following I do not understand completely... # libQtCore.so uses some hardcoded library path inside which are incorrect after copying the file RPi file system # Therefore, an extra path is needed on which to find the required libraries IF ( EXISTS ${CMAKE_FIND_ROOT_PATH}/lib/arm-linux-gnueabihf ) @@ -19,7 +19,9 @@ set(hyperion-remote_SOURCES add_executable(${PROJECT_NAME} ${hyperion-remote_HEADERS} - ${hyperion-remote_SOURCES}) + ${hyperion-remote_SOURCES} + ${WIN_RC_ICON_FILE} +) target_link_libraries(${PROJECT_NAME} effectengine @@ -36,10 +38,14 @@ if (ENABLE_AMLOGIC) ) endif() -install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" ) +if(NOT WIN32) + install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_remote" ) +else() + install ( TARGETS ${PROJECT_NAME} DESTINATION "bin" COMPONENT "hyperion_remote" ) +endif() 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}" ) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_remote" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_remote" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_remote" ) endif(CMAKE_HOST_UNIX) diff --git a/src/hyperion-remote/hyperion-remote.cpp b/src/hyperion-remote/hyperion-remote.cpp index a683ad5b..c73454dc 100644 --- a/src/hyperion-remote/hyperion-remote.cpp +++ b/src/hyperion-remote/hyperion-remote.cpp @@ -58,8 +58,9 @@ int getInstaneIdbyName(const QJsonObject & reply, const QString name){ int main(int argc, char * argv[]) { +#ifndef _WIN32 setenv("AVAHI_COMPAT_NOWARN", "1", 1); - +#endif std::cout << "hyperion-remote:" << std::endl << "\tVersion : " << HYPERION_VERSION << " (" << HYPERION_BUILD_ID << ")" << std::endl diff --git a/src/hyperion-v4l2/CMakeLists.txt b/src/hyperion-v4l2/CMakeLists.txt index 7290cf39..a8056bee 100644 --- a/src/hyperion-v4l2/CMakeLists.txt +++ b/src/hyperion-v4l2/CMakeLists.txt @@ -40,10 +40,10 @@ if (ENABLE_AMLOGIC) ) endif() -install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" ) +install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_v4l2" ) 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}" ) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_v4l2" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_v4l2" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_v4l2" ) endif(CMAKE_HOST_UNIX) diff --git a/src/hyperion-x11/CMakeLists.txt b/src/hyperion-x11/CMakeLists.txt index bb2770ef..56478b6b 100644 --- a/src/hyperion-x11/CMakeLists.txt +++ b/src/hyperion-x11/CMakeLists.txt @@ -38,10 +38,10 @@ target_link_libraries(${PROJECT_NAME} Qt5::Network ) -install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin/" COMPONENT "${PLATFORM}" ) +install ( TARGETS ${PROJECT_NAME} DESTINATION "share/hyperion/bin" COMPONENT "hyperion_x11" ) 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}" ) + install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/${PROJECT_NAME}\" \"${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}\" )" COMPONENT "hyperion_x11" ) + install(FILES "${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME}" DESTINATION "bin" RENAME "${PROJECT_NAME}" COMPONENT "hyperion_x11" ) + install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_${PROJECT_NAME} )" COMPONENT "hyperion_x11" ) endif(CMAKE_HOST_UNIX) diff --git a/src/hyperiond/CMakeLists.txt b/src/hyperiond/CMakeLists.txt index a032c82c..8284bfc5 100644 --- a/src/hyperiond/CMakeLists.txt +++ b/src/hyperiond/CMakeLists.txt @@ -1,178 +1,12 @@ -find_package(PythonLibs 3.4 REQUIRED) +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + find_package(Python3 COMPONENTS Interpreter Development REQUIRED) + include_directories(${Python3_INCLUDE_DIRS} ${Python3_INCLUDE_DIRS}/..) +else() + find_package (PythonLibs ${PYTHON_VERSION_STRING} EXACT) # Maps PythonLibs to the PythonInterp version of the main cmake + include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) +endif() + find_package(Qt5Widgets REQUIRED) -include_directories(${PYTHON_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}/..) - -macro(InstallDependencies TARGET INSTALL_COMPONENT) - set(TARGET_FILE ${CMAKE_BINARY_DIR}/bin/${TARGET}) - set(SYSTEM_LIBS_SKIP -# "libbsd" - "libc" -# "libdbus-1" - "libdl" - "libexpat" - "libfontconfig" - "libfreetype" - "libgcc_s" - "libgcrypt" - "libGL" - "libGLdispatch" - "libglib-2" - "libGLX" - "libgpg-error" -# "liblz4" -# "liblzma" - "libm" - "libpthread" - "librt" - "libstdc++" -# "libsystemd" - "libudev" - "libusb-1" - "libutil" - "libX11" -# "libXau" -# "libxcb" -# "libXdmcp" -# "libXext" -# "libXrender" - "libz" - ) - - if(EXISTS ${TARGET_FILE}) - include(GetPrerequisites) - - if (APPLE) - set(OPENSSL_ROOT_DIR /usr/local/opt/openssl) - endif(APPLE) - - # Extract dependencies ignoring the system ones - get_prerequisites(${TARGET_FILE} DEPENDENCIES 0 1 "" "") - - # Append symlink and non-symlink dependencies to the list - set(PREREQUISITE_LIBS "") - foreach(DEPENDENCY ${DEPENDENCIES}) - get_filename_component(resolved ${DEPENDENCY} NAME_WE) - list(FIND SYSTEM_LIBS_SKIP ${resolved} _index) - if (${_index} GREATER -1) - continue() # Skip system libraries - else() - gp_resolve_item("${TARGET_FILE}" "${DEPENDENCY}" "" "" resolved_file) - get_filename_component(resolved_file ${resolved_file} ABSOLUTE) - gp_append_unique(PREREQUISITE_LIBS ${resolved_file}) - get_filename_component(file_canonical ${resolved_file} REALPATH) - gp_append_unique(PREREQUISITE_LIBS ${file_canonical}) - endif() - endforeach() - - # Append the OpenSSL library to the list - find_package(OpenSSL 1.0.0 REQUIRED) - if (OPENSSL_FOUND) - foreach(openssl_lib ${OPENSSL_LIBRARIES}) - get_prerequisites(${openssl_lib} openssl_deps 0 1 "" "") - - foreach(openssl_dep ${openssl_deps}) - get_filename_component(resolved ${openssl_dep} NAME_WE) - list(FIND SYSTEM_LIBS_SKIP ${resolved} _index) - if (${_index} GREATER -1) - continue() # Skip system libraries - else() - gp_resolve_item("${openssl_lib}" "${openssl_dep}" "" "" resolved_file) - get_filename_component(resolved_file ${resolved_file} ABSOLUTE) - gp_append_unique(PREREQUISITE_LIBS ${resolved_file}) - get_filename_component(file_canonical ${resolved_file} REALPATH) - gp_append_unique(PREREQUISITE_LIBS ${file_canonical}) - endif() - endforeach() - - gp_append_unique(PREREQUISITE_LIBS ${openssl_lib}) - get_filename_component(file_canonical ${openssl_lib} REALPATH) - gp_append_unique(PREREQUISITE_LIBS ${file_canonical}) - endforeach() - endif(OPENSSL_FOUND) - - # Detect the Qt5 plugin directory, source: https://github.com/lxde/lxqt-qtplugin/blob/master/src/CMakeLists.txt - get_target_property(QT_QMAKE_EXECUTABLE ${Qt5Core_QMAKE_EXECUTABLE} IMPORTED_LOCATION) - execute_process( - COMMAND ${QT_QMAKE_EXECUTABLE} -query QT_INSTALL_PLUGINS - OUTPUT_VARIABLE QT_PLUGINS_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - # Copy Qt plugins to 'share/hyperion/lib' - if(QT_PLUGINS_DIR) - foreach(PLUGIN "platforms" "sqldrivers" "imageformats") - if(EXISTS ${QT_PLUGINS_DIR}/${PLUGIN}) - file(GLOB files "${QT_PLUGINS_DIR}/${PLUGIN}/*") - foreach(file ${files}) - get_prerequisites(${file} PLUGINS 0 1 "" "") - - foreach(DEPENDENCY ${PLUGINS}) - get_filename_component(resolved ${DEPENDENCY} NAME_WE) - list(FIND SYSTEM_LIBS_SKIP ${resolved} _index) - if (${_index} GREATER -1) - continue() # Skip system libraries - else() - gp_resolve_item("${file}" "${DEPENDENCY}" "" "" resolved_file) - get_filename_component(resolved_file ${resolved_file} ABSOLUTE) - gp_append_unique(PREREQUISITE_LIBS ${resolved_file}) - get_filename_component(file_canonical ${resolved_file} REALPATH) - gp_append_unique(PREREQUISITE_LIBS ${file_canonical}) - endif() - endforeach() - - install( - FILES ${file} - DESTINATION "share/hyperion/lib/${PLUGIN}" - COMPONENT "${INSTALL_COMPONENT}" - ) - endforeach() - endif() - endforeach() - endif(QT_PLUGINS_DIR) - - # Create a qt.conf file in 'share/hyperion/bin' to override hard-coded search paths in Qt plugins - file(WRITE "${CMAKE_BINARY_DIR}/qt.conf" "[Paths]\nPlugins=../lib/\n") - install( - FILES "${CMAKE_BINARY_DIR}/qt.conf" - DESTINATION "share/hyperion/bin" - COMPONENT "${INSTALL_COMPONENT}" - ) - - # Copy dependencies to 'share/hyperion/lib' - foreach(PREREQUISITE_LIB ${PREREQUISITE_LIBS}) - install( - FILES ${PREREQUISITE_LIB} - DESTINATION "share/hyperion/lib" - COMPONENT "${INSTALL_COMPONENT}" - ) - endforeach() - - # Detect the Python modules directory - execute_process( - COMMAND ${PYTHON_EXECUTABLE} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(standard_lib=True))" - OUTPUT_VARIABLE PYTHON_MODULES_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - # Copy Python modules to 'share/hyperion/lib/python' - if (PYTHON_MODULES_DIR) - install( - DIRECTORY ${PYTHON_MODULES_DIR}/ - DESTINATION "share/hyperion/lib/python" - COMPONENT "${INSTALL_COMPONENT}" - ) - endif(PYTHON_MODULES_DIR) - else() - # Run CMake after target was built to run get_prerequisites on ${TARGET_FILE} - add_custom_command( - TARGET ${TARGET} POST_BUILD - COMMAND ${CMAKE_COMMAND} - ARGS ${CMAKE_SOURCE_DIR} - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - VERBATIM - ) - endif() -endmacro() add_executable(hyperiond hyperiond.h @@ -180,6 +14,7 @@ add_executable(hyperiond hyperiond.cpp systray.cpp main.cpp + ${WIN_RC_ICON_FILE} ) target_link_libraries(hyperiond @@ -190,15 +25,23 @@ target_link_libraries(hyperiond flatbufserver protoserver webserver - bonjour ssdp database python resources - ${PYTHON_LIBRARIES} Qt5::Widgets ) +if (NOT CMAKE_VERSION VERSION_LESS "3.12") + target_link_libraries( hyperiond ${Python3_LIBRARIES} ) +else() + target_link_libraries( hyperiond ${PYTHON_LIBRARIES} ) +endif() + +if (ENABLE_AVAHI) + target_link_libraries(hyperiond bonjour) +endif () + if (ENABLE_AMLOGIC) target_link_libraries(hyperiond Qt5::Core @@ -241,23 +84,35 @@ 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}" ) -install ( FILES ${CMAKE_SOURCE_DIR}/effects/readme.txt DESTINATION "share/hyperion/effects" COMPONENT "${PLATFORM}" ) -install ( FILES ${CMAKE_SOURCE_DIR}/resources/icons/hyperion-icon-32px.png DESTINATION "share/hyperion/icons" COMPONENT "${PLATFORM}" ) +if(NOT WIN32) + install ( TARGETS hyperiond DESTINATION "share/hyperion/bin" COMPONENT "Hyperion" ) + install ( DIRECTORY ${CMAKE_SOURCE_DIR}/bin/service DESTINATION "share/hyperion/" COMPONENT "Hyperion" ) + install ( FILES ${CMAKE_SOURCE_DIR}/effects/readme.txt DESTINATION "share/hyperion/effects" COMPONENT "Hyperion" ) + install ( FILES ${CMAKE_SOURCE_DIR}/resources/icons/hyperion-icon-32px.png DESTINATION "share/hyperion/icons" COMPONENT "Hyperion" ) -# Desktop file for hyperiond -install ( FILES ${CMAKE_SOURCE_DIR}/cmake/desktop/hyperiond_128.png DESTINATION "share/hyperion/desktop" COMPONENT "${PLATFORM}" ) -install ( FILES ${CMAKE_SOURCE_DIR}/cmake/desktop/hyperiond.desktop DESTINATION "share/hyperion/desktop" COMPONENT "${PLATFORM}" ) + # Desktop file for hyperiond + install ( FILES ${CMAKE_SOURCE_DIR}/cmake/desktop/hyperiond_128.png DESTINATION "share/hyperion/desktop" COMPONENT "Hyperion" ) + install ( FILES ${CMAKE_SOURCE_DIR}/cmake/desktop/hyperiond.desktop DESTINATION "share/hyperion/desktop" COMPONENT "Hyperion" ) +else() + install ( TARGETS hyperiond DESTINATION "bin" COMPONENT "Hyperion" ) + install ( FILES ${CMAKE_SOURCE_DIR}/effects/readme.txt DESTINATION "effects" COMPONENT "Hyperion" ) + + #set( CMAKE_INSTALL_UCRT_LIBRARIES TRUE ) + #set( CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE ) + #include( InstallRequiredSystemLibraries ) +endif() if(CMAKE_HOST_UNIX) - install(CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/hyperiond\" \"${CMAKE_BINARY_DIR}/symlink_hyperiond\" )" COMPONENT "${PLATFORM}" ) - install(FILES ${CMAKE_BINARY_DIR}/symlink_hyperiond DESTINATION "bin" RENAME hyperiond COMPONENT "${PLATFORM}" ) - install(CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_hyperiond )" COMPONENT "${PLATFORM}" ) -endif(CMAKE_HOST_UNIX) - + install( CODE "EXECUTE_PROCESS(COMMAND ln -sf \"../share/hyperion/bin/hyperiond\" \"${CMAKE_BINARY_DIR}/symlink_hyperiond\" )" COMPONENT "Hyperion" ) + install( FILES ${CMAKE_BINARY_DIR}/symlink_hyperiond DESTINATION "bin" RENAME hyperiond COMPONENT "Hyperion" ) + install( CODE "FILE (REMOVE ${CMAKE_BINARY_DIR}/symlink_hyperiond )" COMPONENT "Hyperion" ) +endif() # Copy dependencies (not for OSX) -if (NOT ENABLE_OSX) - InstallDependencies("hyperiond" ${PLATFORM}) +include(${CMAKE_SOURCE_DIR}/cmake/Dependencies.cmake) + +if (NOT ENABLE_OSX AND NOT WIN32) # Unix + DeployUnix("hyperiond") +elseif(WIN32) # Windows + DeployWindows("hyperiond") endif () diff --git a/src/hyperiond/detectProcess.h b/src/hyperiond/detectProcess.h new file mode 100644 index 00000000..2937ae2e --- /dev/null +++ b/src/hyperiond/detectProcess.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +#ifdef WIN32 +// psapi.h requires windows.h to be included +#include +#include +#endif + +unsigned int getProcessIdsByProcessName(const char *processName, QStringList &listOfPids) +{ + + // Clear content of returned list of PIDS + listOfPids.clear(); + +#if defined(WIN32) + // Get the list of process identifiers. + DWORD aProcesses[1024], cbNeeded, cProcesses; + unsigned int i; + + if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) + return 0; + + // Calculate how many process identifiers were returned. + cProcesses = cbNeeded / sizeof(DWORD); + + // Search for a matching name for each process + for (i = 0; i < cProcesses; i++) + { + if (aProcesses[i] != 0) + { + char szProcessName[MAX_PATH] = {0}; + + DWORD processID = aProcesses[i]; + + // Get a handle to the process. + HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); + + // Get the process name + if (NULL != hProcess) + { + HMODULE hMod; + DWORD cbNeeded; + + if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) + GetModuleBaseNameA(hProcess, hMod, szProcessName, sizeof(szProcessName) / sizeof(char)); + + // Release the handle to the process. + CloseHandle(hProcess); + + if (*szProcessName != 0 && strcmp(processName, szProcessName) == 0) + listOfPids.append(QString::number(processID)); + } + } + } + + return listOfPids.count(); + +#else + + // Run pgrep, which looks through the currently running processses and lists the process IDs + // which match the selection criteria to stdout. + QProcess process; + process.start("pgrep", QStringList() << processName); + process.waitForReadyRead(); + + QByteArray bytes = process.readAllStandardOutput(); + + process.terminate(); + process.waitForFinished(); + process.kill(); + + // Output is something like "2472\n2323" for multiple instances + if (bytes.isEmpty()) + return 0; + + listOfPids = QString(bytes).split("\n", QString::SkipEmptyParts); + return listOfPids.count(); + +#endif +} diff --git a/src/hyperiond/hyperiond.cpp b/src/hyperiond/hyperiond.cpp index 38918b59..292840b4 100644 --- a/src/hyperiond/hyperiond.cpp +++ b/src/hyperiond/hyperiond.cpp @@ -1,4 +1,3 @@ -#include #include #include @@ -18,12 +17,14 @@ #include #include -// bonjour browser -#include +#include // Required to determine the cmake options +// bonjour browser +#ifdef ENABLE_AVAHI +#include +#endif #include #include -#include // Required to determine the cmake options #include "hyperiond.h" // Flatbuffer Server @@ -53,28 +54,29 @@ // EffectFileHandler #include -HyperionDaemon* HyperionDaemon::daemon = nullptr; +HyperionDaemon *HyperionDaemon::daemon = nullptr; -HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bool& logLvlOverwrite) - : QObject(parent) - , _log(Logger::getInstance("DAEMON")) - , _instanceManager(new HyperionIManager(rootPath, this)) - , _authManager(new AuthManager(this)) - , _bonjourBrowserWrapper(new BonjourBrowserWrapper()) - , _netOrigin(new NetOrigin(this)) - , _pyInit(new PythonInit()) - , _webserver(nullptr) - , _sslWebserver(nullptr) - , _jsonServer(nullptr) - , _v4l2Grabber(nullptr) - , _dispmanx(nullptr) - , _x11Grabber(nullptr) - , _amlGrabber(nullptr) - , _fbGrabber(nullptr) - , _osxGrabber(nullptr) - , _qtGrabber(nullptr) - , _ssdp(nullptr) - , _currVideoMode(VIDEO_2D) +HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bool &logLvlOverwrite) + : QObject(parent), _log(Logger::getInstance("DAEMON")) + , _instanceManager(new HyperionIManager(rootPath, this)) + , _authManager(new AuthManager(this)) +#ifdef ENABLE_AVAHI + , _bonjourBrowserWrapper(new BonjourBrowserWrapper()) +#endif + , _netOrigin(new NetOrigin(this)) + , _pyInit(new PythonInit()) + , _webserver(nullptr) + , _sslWebserver(nullptr) + , _jsonServer(nullptr) + , _v4l2Grabber(nullptr) + , _dispmanx(nullptr) + , _x11Grabber(nullptr) + , _amlGrabber(nullptr) + , _fbGrabber(nullptr) + , _osxGrabber(nullptr) + , _qtGrabber(nullptr) + , _ssdp(nullptr) + , _currVideoMode(VIDEO_2D) { HyperionDaemon::daemon = this; @@ -83,18 +85,18 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bo qRegisterMetaType("hyperion::Components"); qRegisterMetaType("settings::type"); qRegisterMetaType("VideoMode"); - qRegisterMetaType>("QMap"); + qRegisterMetaType>("QMap"); qRegisterMetaType>("std::vector"); // init settings - _settingsManager = new SettingsManager(0,this); + _settingsManager = new SettingsManager(0, this); // set inital log lvl if the loglvl wasn't overwritten by arg - if(!logLvlOverwrite) + if (!logLvlOverwrite) handleSettingsUpdate(settings::LOGGER, getSetting(settings::LOGGER)); // init EffectFileHandler - EffectFileHandler* efh = new EffectFileHandler(rootPath, getSetting(settings::EFFECTS), this); + EffectFileHandler *efh = new EffectFileHandler(rootPath, getSetting(settings::EFFECTS), this); connect(this, &HyperionDaemon::settingsChanged, efh, &EffectFileHandler::handleSettingsUpdate); // connect and apply settings for AuthManager @@ -122,10 +124,10 @@ HyperionDaemon::HyperionDaemon(const QString rootPath, QObject *parent, const bo // return videoMode changes from Daemon to HyperionIManager connect(this, &HyperionDaemon::videoMode, _instanceManager, &HyperionIManager::newVideoMode); - // ---- grabber ----- - #if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_AMLOGIC) - Warning(_log, "No platform capture can be instantiated, because all grabbers have been left out from the build"); - #endif +// ---- grabber ----- +#if !defined(ENABLE_DISPMANX) && !defined(ENABLE_OSX) && !defined(ENABLE_FB) && !defined(ENABLE_X11) && !defined(ENABLE_AMLOGIC) && !defined(ENABLE_QT) + Warning(_log, "No platform capture can be instantiated, because all grabbers have been left out from the build"); +#endif // init system capture (framegrabber) handleSettingsUpdate(settings::SYSTEMCAPTURE, getSetting(settings::SYSTEMCAPTURE)); @@ -144,9 +146,9 @@ HyperionDaemon::~HyperionDaemon() delete _pyInit; } -void HyperionDaemon::setVideoMode(const VideoMode& mode) +void HyperionDaemon::setVideoMode(const VideoMode &mode) { - if(_currVideoMode != mode) + if (_currVideoMode != mode) { _currVideoMode = mode; emit videoMode(mode); @@ -177,7 +179,9 @@ void HyperionDaemon::freeObjects() // stop Hyperions (non blocking) _instanceManager->stopAll(); +#ifdef ENABLE_AVAHI delete _bonjourBrowserWrapper; +#endif delete _amlGrabber; delete _dispmanx; delete _fbGrabber; @@ -185,19 +189,21 @@ void HyperionDaemon::freeObjects() delete _qtGrabber; delete _v4l2Grabber; - _v4l2Grabber = nullptr; + _v4l2Grabber = nullptr; +#ifdef ENABLE_AVAHI _bonjourBrowserWrapper = nullptr; - _amlGrabber = nullptr; - _dispmanx = nullptr; - _fbGrabber = nullptr; - _osxGrabber = nullptr; - _qtGrabber = nullptr; - _flatBufferServer = nullptr; - _protoServer = nullptr; - _ssdp = nullptr; - _webserver = nullptr; - _sslWebserver = nullptr; - _jsonServer = nullptr; +#endif + _amlGrabber = nullptr; + _dispmanx = nullptr; + _fbGrabber = nullptr; + _osxGrabber = nullptr; + _qtGrabber = nullptr; + _flatBufferServer = nullptr; + _protoServer = nullptr; + _ssdp = nullptr; + _webserver = nullptr; + _sslWebserver = nullptr; + _jsonServer = nullptr; } void HyperionDaemon::startNetworkServices() @@ -208,93 +214,97 @@ void HyperionDaemon::startNetworkServices() // Create FlatBuffer server in thread _flatBufferServer = new FlatBufferServer(getSetting(settings::FLATBUFSERVER)); - QThread* fbThread = new QThread(this); + QThread *fbThread = new QThread(this); _flatBufferServer->moveToThread(fbThread); - connect( fbThread, &QThread::started, _flatBufferServer, &FlatBufferServer::initServer ); - connect( fbThread, &QThread::finished, _flatBufferServer, &QObject::deleteLater ); - connect( fbThread, &QThread::finished, fbThread, &QObject::deleteLater ); + connect(fbThread, &QThread::started, _flatBufferServer, &FlatBufferServer::initServer); + connect(fbThread, &QThread::finished, _flatBufferServer, &QObject::deleteLater); + connect(fbThread, &QThread::finished, fbThread, &QObject::deleteLater); connect(this, &HyperionDaemon::settingsChanged, _flatBufferServer, &FlatBufferServer::handleSettingsUpdate); fbThread->start(); // Create Proto server in thread _protoServer = new ProtoServer(getSetting(settings::PROTOSERVER)); - QThread* pThread = new QThread(this); + QThread *pThread = new QThread(this); _protoServer->moveToThread(pThread); - connect( pThread, &QThread::started, _protoServer, &ProtoServer::initServer ); - connect( pThread, &QThread::finished, _protoServer, &QObject::deleteLater ); - connect( pThread, &QThread::finished, pThread, &QObject::deleteLater ); - connect( this, &HyperionDaemon::settingsChanged, _protoServer, &ProtoServer::handleSettingsUpdate ); + connect(pThread, &QThread::started, _protoServer, &ProtoServer::initServer); + connect(pThread, &QThread::finished, _protoServer, &QObject::deleteLater); + connect(pThread, &QThread::finished, pThread, &QObject::deleteLater); + connect(this, &HyperionDaemon::settingsChanged, _protoServer, &ProtoServer::handleSettingsUpdate); pThread->start(); // Create Webserver in thread _webserver = new WebServer(getSetting(settings::WEBSERVER), false); - QThread* wsThread = new QThread(this); + QThread *wsThread = new QThread(this); _webserver->moveToThread(wsThread); - connect( wsThread, &QThread::started, _webserver, &WebServer::initServer ); - connect( wsThread, &QThread::finished, _webserver, &QObject::deleteLater ); - connect( wsThread, &QThread::finished, wsThread, &QObject::deleteLater ); + connect(wsThread, &QThread::started, _webserver, &WebServer::initServer); + connect(wsThread, &QThread::finished, _webserver, &QObject::deleteLater); + connect(wsThread, &QThread::finished, wsThread, &QObject::deleteLater); connect(this, &HyperionDaemon::settingsChanged, _webserver, &WebServer::handleSettingsUpdate); wsThread->start(); // Create SSL Webserver in thread _sslWebserver = new WebServer(getSetting(settings::WEBSERVER), true); - QThread* sslWsThread = new QThread(this); + QThread *sslWsThread = new QThread(this); _sslWebserver->moveToThread(sslWsThread); - connect( sslWsThread, &QThread::started, _sslWebserver, &WebServer::initServer ); - connect( sslWsThread, &QThread::finished, _sslWebserver, &QObject::deleteLater ); - connect( sslWsThread, &QThread::finished, sslWsThread, &QObject::deleteLater ); + connect(sslWsThread, &QThread::started, _sslWebserver, &WebServer::initServer); + connect(sslWsThread, &QThread::finished, _sslWebserver, &QObject::deleteLater); + connect(sslWsThread, &QThread::finished, sslWsThread, &QObject::deleteLater); connect(this, &HyperionDaemon::settingsChanged, _sslWebserver, &WebServer::handleSettingsUpdate); sslWsThread->start(); // Create SSDP server in thread - _ssdp = new SSDPHandler(_webserver, getSetting(settings::FLATBUFSERVER).object()["port"].toInt(), getSetting(settings::JSONSERVER).object()["port"].toInt(), getSetting(settings::GENERAL).object()["name"].toString()); - QThread* ssdpThread = new QThread(this); + _ssdp = new SSDPHandler(_webserver, getSetting(settings::FLATBUFSERVER).object()["port"].toInt(), getSetting(settings::JSONSERVER).object()["port"].toInt(), getSetting(settings::GENERAL).object()["name"].toString()); + QThread *ssdpThread = new QThread(this); _ssdp->moveToThread(ssdpThread); - connect( ssdpThread, &QThread::started, _ssdp, &SSDPHandler::initServer ); - connect( ssdpThread, &QThread::finished, _ssdp, &QObject::deleteLater ); - connect( ssdpThread, &QThread::finished, ssdpThread, &QObject::deleteLater ); - connect( _webserver, &WebServer::stateChange, _ssdp, &SSDPHandler::handleWebServerStateChange); + connect(ssdpThread, &QThread::started, _ssdp, &SSDPHandler::initServer); + connect(ssdpThread, &QThread::finished, _ssdp, &QObject::deleteLater); + connect(ssdpThread, &QThread::finished, ssdpThread, &QObject::deleteLater); + connect(_webserver, &WebServer::stateChange, _ssdp, &SSDPHandler::handleWebServerStateChange); connect(this, &HyperionDaemon::settingsChanged, _ssdp, &SSDPHandler::handleSettingsUpdate); ssdpThread->start(); } -void HyperionDaemon::handleSettingsUpdate(const settings::type& settingsType, const QJsonDocument& config) +void HyperionDaemon::handleSettingsUpdate(const settings::type &settingsType, const QJsonDocument &config) { - if(settingsType == settings::LOGGER) + if (settingsType == settings::LOGGER) { - const QJsonObject & logConfig = config.object(); + const QJsonObject &logConfig = config.object(); std::string level = logConfig["level"].toString("warn").toStdString(); // silent warn verbose debug - if (level == "silent") Logger::setLogLevel(Logger::OFF); - else if (level == "warn") Logger::setLogLevel(Logger::WARNING); - else if (level == "verbose") Logger::setLogLevel(Logger::INFO); - else if (level == "debug") Logger::setLogLevel(Logger::DEBUG); + if (level == "silent") + Logger::setLogLevel(Logger::OFF); + else if (level == "warn") + Logger::setLogLevel(Logger::WARNING); + else if (level == "verbose") + Logger::setLogLevel(Logger::INFO); + else if (level == "debug") + Logger::setLogLevel(Logger::DEBUG); } - if(settingsType == settings::SYSTEMCAPTURE) + if (settingsType == settings::SYSTEMCAPTURE) { - const QJsonObject & grabberConfig = config.object(); + const QJsonObject &grabberConfig = config.object(); - _grabber_width = grabberConfig["width"].toInt(96); - _grabber_height = grabberConfig["height"].toInt(96); + _grabber_width = grabberConfig["width"].toInt(96); + _grabber_height = grabberConfig["height"].toInt(96); _grabber_frequency = grabberConfig["frequency_Hz"].toInt(10); - _grabber_cropLeft = grabberConfig["cropLeft"].toInt(0); - _grabber_cropRight = grabberConfig["cropRight"].toInt(0); - _grabber_cropTop = grabberConfig["cropTop"].toInt(0); + _grabber_cropLeft = grabberConfig["cropLeft"].toInt(0); + _grabber_cropRight = grabberConfig["cropRight"].toInt(0); + _grabber_cropTop = grabberConfig["cropTop"].toInt(0); _grabber_cropBottom = grabberConfig["cropBottom"].toInt(0); - _grabber_ge2d_mode = grabberConfig["ge2d_mode"].toInt(0); - _grabber_device = grabberConfig["amlogic_grabber"].toString("amvideocap0"); + _grabber_ge2d_mode = grabberConfig["ge2d_mode"].toInt(0); + _grabber_device = grabberConfig["amlogic_grabber"].toString("amvideocap0"); - #ifdef ENABLE_OSX - QString type = "osx"; - #else - QString type = grabberConfig["type"].toString("auto"); - #endif +#ifdef ENABLE_OSX + QString type = "osx"; +#else + QString type = grabberConfig["type"].toString("auto"); +#endif // auto eval of type - if ( type == "auto" ) + if (type == "auto") { // dispmanx -> on raspi if (QFile::exists("/dev/vchiq")) @@ -302,15 +312,17 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& settingsType, co type = "dispmanx"; } // amlogic -> /dev/amvideo exists - else if ( QFile::exists("/dev/amvideo") ) + else if (QFile::exists("/dev/amvideo")) { type = "amlogic"; - if ( !QFile::exists("/dev/" + _grabber_device) ) - { Error( _log, "grabber device '%s' for type amlogic not found!", QSTRING_CSTR(_grabber_device)); } + if (!QFile::exists("/dev/" + _grabber_device)) + { + Error(_log, "grabber device '%s' for type amlogic not found!", QSTRING_CSTR(_grabber_device)); + } } // x11 -> if DISPLAY is set - else if (getenv("DISPLAY") != NULL ) + else if (getenv("DISPLAY") != NULL) { type = "x11"; } @@ -321,9 +333,9 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& settingsType, co } } - if(_prevType != type) + if (_prevType != type) { - Info( _log, "set screen capture device to '%s'", QSTRING_CSTR(type)); + Info(_log, "set screen capture device to '%s'", QSTRING_CSTR(type)); // stop all capture interfaces #ifdef ENABLE_FB @@ -376,49 +388,49 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& settingsType, co #endif // create/start capture interface - if(type == "framebuffer") + if (type == "framebuffer") { - if(_fbGrabber == nullptr) + if (_fbGrabber == nullptr) createGrabberFramebuffer(grabberConfig); #ifdef ENABLE_FB _fbGrabber->tryStart(); #endif } - else if(type == "dispmanx") + else if (type == "dispmanx") { - if(_dispmanx == nullptr) + if (_dispmanx == nullptr) createGrabberDispmanx(); #ifdef ENABLE_DISPMANX _dispmanx->tryStart(); #endif } - else if(type == "amlogic") + else if (type == "amlogic") { - if(_amlGrabber == nullptr) + if (_amlGrabber == nullptr) createGrabberAmlogic(); #ifdef ENABLE_AMLOGIC _amlGrabber->tryStart(); #endif } - else if(type == "osx") + else if (type == "osx") { - if(_osxGrabber == nullptr) + if (_osxGrabber == nullptr) createGrabberOsx(grabberConfig); #ifdef ENABLE_OSX _osxGrabber->tryStart(); #endif } - else if(type == "x11") + else if (type == "x11") { - if(_x11Grabber == nullptr) + if (_x11Grabber == nullptr) createGrabberX11(grabberConfig); #ifdef ENABLE_X11 _x11Grabber->tryStart(); #endif } - else if(type == "qt") + else if (type == "qt") { - if(_qtGrabber == nullptr) + if (_qtGrabber == nullptr) createGrabberQt(grabberConfig); #ifdef ENABLE_QT _qtGrabber->tryStart(); @@ -426,22 +438,22 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& settingsType, co } else { - Error(_log,"Unknown platform capture type: %s", QSTRING_CSTR(type)); + Error(_log, "Unknown platform capture type: %s", QSTRING_CSTR(type)); return; } _prevType = type; } } - else if(settingsType == settings::V4L2) + else if (settingsType == settings::V4L2) { #ifdef ENABLE_V4L2 - if(_v4l2Grabber != nullptr) - return; + if (_v4l2Grabber != nullptr) + return; - const QJsonObject & grabberConfig = config.object(); + const QJsonObject &grabberConfig = config.object(); - _v4l2Grabber = new V4L2Wrapper( + _v4l2Grabber = new V4L2Wrapper( grabberConfig["device"].toString("auto"), grabberConfig["width"].toInt(0), grabberConfig["height"].toInt(0), @@ -449,22 +461,23 @@ void HyperionDaemon::handleSettingsUpdate(const settings::type& settingsType, co parseVideoStandard(grabberConfig["standard"].toString("no-change")), parsePixelFormat(grabberConfig["pixelFormat"].toString("no-change")), grabberConfig["sizeDecimation"].toInt(8)); - _v4l2Grabber->setSignalThreshold( - grabberConfig["redSignalThreshold"].toDouble(0.0)/100.0, - grabberConfig["greenSignalThreshold"].toDouble(0.0)/100.0, - grabberConfig["blueSignalThreshold"].toDouble(0.0)/100.0); - _v4l2Grabber->setCropping( + + _v4l2Grabber->setSignalThreshold( + grabberConfig["redSignalThreshold"].toDouble(0.0) / 100.0, + grabberConfig["greenSignalThreshold"].toDouble(0.0) / 100.0, + grabberConfig["blueSignalThreshold"].toDouble(0.0) / 100.0); + _v4l2Grabber->setCropping( grabberConfig["cropLeft"].toInt(0), grabberConfig["cropRight"].toInt(0), grabberConfig["cropTop"].toInt(0), grabberConfig["cropBottom"].toInt(0)); - _v4l2Grabber->setSignalDetectionEnable(grabberConfig["signalDetection"].toBool(true)); - _v4l2Grabber->setSignalDetectionOffset( + _v4l2Grabber->setSignalDetectionEnable(grabberConfig["signalDetection"].toBool(true)); + _v4l2Grabber->setSignalDetectionOffset( grabberConfig["sDHOffsetMin"].toDouble(0.25), grabberConfig["sDVOffsetMin"].toDouble(0.25), grabberConfig["sDHOffsetMax"].toDouble(0.75), grabberConfig["sDVOffsetMax"].toDouble(0.75)); - Debug(_log, "V4L2 grabber created"); + Debug(_log, "V4L2 grabber created"); // connect to HyperionDaemon signal connect(this, &HyperionDaemon::videoMode, _v4l2Grabber, &V4L2Wrapper::setVideoMode); @@ -491,7 +504,6 @@ void HyperionDaemon::createGrabberDispmanx() #endif } - void HyperionDaemon::createGrabberAmlogic() { #ifdef ENABLE_AMLOGIC @@ -508,13 +520,13 @@ void HyperionDaemon::createGrabberAmlogic() #endif } -void HyperionDaemon::createGrabberX11(const QJsonObject & grabberConfig) +void HyperionDaemon::createGrabberX11(const QJsonObject &grabberConfig) { #ifdef ENABLE_X11 _x11Grabber = new X11Wrapper( - _grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom, - grabberConfig["pixelDecimation"].toInt(8), - _grabber_frequency ); + _grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom, + grabberConfig["pixelDecimation"].toInt(8), + _grabber_frequency); _x11Grabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); // connect to HyperionDaemon signal @@ -527,14 +539,14 @@ void HyperionDaemon::createGrabberX11(const QJsonObject & grabberConfig) #endif } -void HyperionDaemon::createGrabberQt(const QJsonObject & grabberConfig) +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 ); + _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); @@ -546,13 +558,13 @@ void HyperionDaemon::createGrabberQt(const QJsonObject & grabberConfig) #endif } -void HyperionDaemon::createGrabberFramebuffer(const QJsonObject & grabberConfig) +void HyperionDaemon::createGrabberFramebuffer(const QJsonObject &grabberConfig) { #ifdef ENABLE_FB // Construct and start the framebuffer grabber if the configuration is present _fbGrabber = new FramebufferWrapper( - grabberConfig["device"].toString("/dev/fb0"), - _grabber_width, _grabber_height, _grabber_frequency); + grabberConfig["device"].toString("/dev/fb0"), + _grabber_width, _grabber_height, _grabber_frequency); _fbGrabber->setCropping(_grabber_cropLeft, _grabber_cropRight, _grabber_cropTop, _grabber_cropBottom); // connect to HyperionDaemon signal connect(this, &HyperionDaemon::videoMode, _fbGrabber, &FramebufferWrapper::setVideoMode); @@ -564,14 +576,13 @@ void HyperionDaemon::createGrabberFramebuffer(const QJsonObject & grabberConfig) #endif } - -void HyperionDaemon::createGrabberOsx(const QJsonObject & grabberConfig) +void HyperionDaemon::createGrabberOsx(const QJsonObject &grabberConfig) { #ifdef ENABLE_OSX // Construct and start the osx grabber if the configuration is present _osxGrabber = new OsxWrapper( - grabberConfig["display"].toInt(0), - _grabber_width, _grabber_height, _grabber_frequency); + grabberConfig["display"].toInt(0), + _grabber_width, _grabber_height, _grabber_frequency); // connect to HyperionDaemon signal connect(this, &HyperionDaemon::videoMode, _osxGrabber, &OsxWrapper::setVideoMode); diff --git a/src/hyperiond/main.cpp b/src/hyperiond/main.cpp index 25f3e21f..6da2b2a0 100644 --- a/src/hyperiond/main.cpp +++ b/src/hyperiond/main.cpp @@ -1,13 +1,19 @@ #include #include -#include #include #include -#ifndef __APPLE__ +#if !defined(__APPLE__) && !defined(_WIN32) /* prctl is Linux only */ #include #endif +// getpid() +#ifdef _WIN32 +#include +//#include +#else +#include +#endif #include @@ -20,7 +26,6 @@ #include #include #include -#include #include "HyperionConfig.h" @@ -30,6 +35,8 @@ #include #include <../../include/db/AuthTable.h> +#include "detectProcess.h" + #ifdef ENABLE_X11 #include #endif @@ -41,84 +48,13 @@ using namespace commandline; #define PERM0664 QFileDevice::ReadOwner | QFileDevice::ReadGroup | QFileDevice::ReadOther | QFileDevice::WriteOwner | QFileDevice::WriteGroup -unsigned int getProcessIdsByProcessName(const char* processName, QStringList &listOfPids) -{ - // Clear content of returned list of PIDS - listOfPids.clear(); - -#if defined(WIN32) - // Get the list of process identifiers. - DWORD aProcesses[1024], cbNeeded, cProcesses; - unsigned int i; - - if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded)) - return 0; - - // Calculate how many process identifiers were returned. - cProcesses = cbNeeded / sizeof(DWORD); - - // Search for a matching name for each process - for (i = 0; i < cProcesses; i++) - { - if (aProcesses[i] != 0) - { - char szProcessName[MAX_PATH] = {0}; - - DWORD processID = aProcesses[i]; - - // Get a handle to the process. - HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); - - // Get the process name - if (NULL != hProcess) - { - HMODULE hMod; - DWORD cbNeeded; - - if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) - GetModuleBaseNameA(hProcess, hMod, szProcessName, sizeof(szProcessName)/sizeof(char)); - - // Release the handle to the process. - CloseHandle(hProcess); - - if (*szProcessName != 0 && strcmp(processName, szProcessName) == 0) - listOfPids.append(QString::number(processID)); - } - } - } - - return listOfPids.count(); - -#else - - // Run pgrep, which looks through the currently running processses and lists the process IDs - // which match the selection criteria to stdout. - QProcess process; - process.start("pgrep", QStringList() << processName); - process.waitForReadyRead(); - - QByteArray bytes = process.readAllStandardOutput(); - - process.terminate(); - process.waitForFinished(); - process.kill(); - - // Output is something like "2472\n2323" for multiple instances - if (bytes.isEmpty()) - return 0; - - listOfPids = QString(bytes).split("\n", QString::SkipEmptyParts); - return listOfPids.count(); - -#endif -} - +#ifndef _WIN32 void signal_handler(const int signum) { // Hyperion Managment instance - HyperionIManager* _hyperion = HyperionIManager::getInstance(); + HyperionIManager *_hyperion = HyperionIManager::getInstance(); - if(signum == SIGCHLD) + if (signum == SIGCHLD) { // only quit when a registered child process is gone // currently this feature is not active ... @@ -146,6 +82,7 @@ void signal_handler(const int signum) // reset signal handler to default (in case this handler is not capable of stopping) signal(signum, SIG_DFL); } +#endif QCoreApplication* createApplication(int &argc, char *argv[]) { @@ -166,7 +103,7 @@ QCoreApplication* createApplication(int &argc, char *argv[]) } // on osx/windows gui always available -#if defined(__APPLE__) || defined(__WIN32__) +#if defined(__APPLE__) || defined(_WIN32) isGuiApp = true && ! forceNoGui; #else if (!forceNoGui) @@ -204,10 +141,15 @@ QCoreApplication* createApplication(int &argc, char *argv[]) int main(int argc, char** argv) { +#ifndef _WIN32 setenv("AVAHI_COMPAT_NOWARN", "1", 1); - +#endif +#ifdef _WIN32 + // We can get a console window also in app gui mode conditional + //AllocConsole(); +#endif // initialize main logger and set global log level - Logger* log = Logger::getInstance("MAIN"); + Logger *log = Logger::getInstance("MAIN"); Logger::setLogLevel(Logger::WARNING); // check if we are running already an instance @@ -215,7 +157,12 @@ int main(int argc, char** argv) // TODO Allow one session per user // http://www.qtcentre.org/threads/44489-Get-Process-ID-for-a-running-application QStringList listOfPids; - if(getProcessIdsByProcessName("hyperiond", listOfPids) > 1) + #ifdef _WIN32 + const char* processName = "hyperiond.exe"; + #else + const char* processName = "hyperiond"; + #endif + if (getProcessIdsByProcessName(processName, listOfPids) > 1) { Error(log, "The Hyperion Daemon is already running, abort start"); return 0; @@ -226,6 +173,7 @@ int main(int argc, char** argv) bool isGuiApp = (qobject_cast(app.data()) != 0 && QSystemTrayIcon::isSystemTrayAvailable()); +#ifndef _WIN32 signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGABRT, signal_handler); @@ -233,7 +181,7 @@ int main(int argc, char** argv) signal(SIGPIPE, signal_handler); signal(SIGUSR1, signal_handler); signal(SIGUSR2, signal_handler); - +#endif // force the locale setlocale(LC_ALL, "C"); QLocale::setDefault(QLocale::c()); diff --git a/src/hyperiond/systray.cpp b/src/hyperiond/systray.cpp index 03d4a4f3..1c50d801 100644 --- a/src/hyperiond/systray.cpp +++ b/src/hyperiond/systray.cpp @@ -1,7 +1,8 @@ #include +#ifndef _WIN32 #include - +#endif #include #include #include @@ -126,6 +127,7 @@ void SysTray::closeEvent(QCloseEvent *event) void SysTray::settings() { +#ifndef _WIN32 // Hide error messages when opening webbrowser int out_pipe[2]; @@ -144,13 +146,16 @@ void SysTray::settings() // redirecting stderr to stdout ::dup2(STDOUT_FILENO, STDERR_FILENO); } + #endif QDesktopServices::openUrl(QUrl("http://localhost:"+QString::number(_webPort)+"/", QUrl::TolerantMode)); - + + #ifndef _WIN32 // restoring stdout ::dup2(saved_stdout, STDOUT_FILENO); // restoring stderr ::dup2(saved_stderr, STDERR_FILENO); + #endif } void SysTray::setEffect()