From 8f6b7f99e3afd1c8cfc50c4c822d4f517b59fbb6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?IOhannes=20m=20zm=C3=B6lnig?= Date: Sun, 17 Jul 2022 21:44:45 +0200 Subject: [PATCH] New upstream version 1.6.1+ds0 --- .clang-format-ignore | 3 + CMakeLists.txt | 35 +- build | 17 +- docs/Build/Linux.md | 4 +- docs/Build/Mac.md | 2 +- docs/Build/Meson_build.md | 4 +- docs/changelog.yml | 9 + faust-src/faust2header.cpp | 5 +- faust-src/zitarevdsp.dsp | 3 + jacktrip.pro | 86 +- linux/flatpak/org.jacktrip.JackTrip.Devel.yml | 20 +- .../org.jacktrip.JackTrip.Devel.yml.j2 | 22 +- linux/flatpak/org.jacktrip.JackTrip.yml | 20 +- .../JackTrip.app_template/Contents/Info.plist | 2 +- macos/assemble_app.sh | 11 +- macos/package/JackTrip.pkgproj_template | 2 +- macos/package/{link.sh => postinstall.sh} | 5 + meson.build | 61 +- meson_options.txt | 2 + releases/edge/mac-manifests.json | 65 + releases/edge/win-manifests.json | 65 + releases/stable/mac-manifests.json | 15 + releases/stable/win-manifests.json | 15 + .../test_hub_mode_server_and_client.sh | 2 +- src/AudioInterface.cpp | 2 +- src/AudioInterface.h | 4 +- src/Auth.cpp | 4 +- src/Compressor.cpp | 2 - src/Compressor.h | 1 + src/DataProtocol.h | 2 +- src/JMess.cpp | 2 +- src/JMess.h | 2 +- src/JackAudioInterface.h | 4 +- src/JackTrip.cpp | 41 +- src/JackTrip.h | 10 +- src/JackTripWorker.h | 4 +- src/Limiter.cpp | 2 - src/Limiter.h | 1 + src/PacketHeader.cpp | 6 +- src/PacketHeader.h | 2 +- src/Regulator.cpp | 433 +++-- src/Regulator.h | 88 +- src/Reverb.cpp | 2 - src/Reverb.h | 2 + src/RingBuffer.cpp | 15 +- src/RingBuffer.h | 6 + src/RtAudioInterface.cpp | 4 +- src/RtAudioInterface.h | 2 +- src/Settings.cpp | 2 +- src/UdpDataProtocol.cpp | 41 +- src/UdpDataProtocol.h | 13 +- src/UdpHubListener.cpp | 6 +- src/build | 2 +- src/dblsqd/feed.cpp | 332 ++++ src/dblsqd/feed.h | 67 + src/dblsqd/release.cpp | 143 ++ src/dblsqd/release.h | 44 + src/dblsqd/semver.cpp | 87 + src/dblsqd/semver.h | 34 + src/dblsqd/update_dialog.cpp | 694 ++++++++ src/dblsqd/update_dialog.h | 106 ++ src/dblsqd/update_dialog.ui | 402 +++++ src/gui/Browse.qml | 372 ++++ src/gui/Connected.qml | 94 + src/gui/FirstLaunch.qml | 139 ++ src/gui/JTOriginal.png | Bin 0 -> 46081 bytes src/gui/JTVS.png | Bin 0 -> 104329 bytes src/gui/Login.qml | 141 ++ src/gui/Poppins-Bold.ttf | Bin 0 -> 153944 bytes src/gui/Poppins-Regular.ttf | Bin 0 -> 158240 bytes src/gui/Settings.qml | 318 ++++ src/gui/Setup.qml | 505 ++++++ src/gui/Studio.qml | 210 +++ src/gui/cog.svg | 3 + src/gui/ethernet.png | Bin 0 -> 4922 bytes src/gui/flags/AE.svg | 1 + src/gui/flags/AU.svg | 1 + src/gui/flags/BE.svg | 1 + src/gui/flags/BR.svg | 1 + src/gui/flags/CA.svg | 1 + src/gui/flags/CH.svg | 1 + src/gui/flags/DE.svg | 1 + src/gui/flags/FR.svg | 1 + src/gui/flags/GB.svg | 1 + src/gui/flags/HK.svg | 1 + src/gui/flags/ID.svg | 1 + src/gui/flags/IT.svg | 1 + src/gui/flags/JP.svg | 1 + src/gui/flags/RO.svg | 1 + src/gui/flags/SE.svg | 1 + src/gui/flags/SG.svg | 1 + src/gui/flags/TW.svg | 1 + src/gui/flags/US.svg | 1 + src/gui/flags/ZA.svg | 1 + src/gui/headphones.svg | 3 + src/gui/jacktrip white.png | Bin 0 -> 11229 bytes src/gui/jacktrip.png | Bin 0 -> 12135 bytes src/gui/join.svg | 3 + src/gui/leave.svg | 3 + src/gui/logo.svg | 3 + src/gui/mic.svg | 4 + src/gui/private.svg | 3 + src/gui/public.svg | 3 + src/gui/qjacktrip.cpp | 74 +- src/gui/qjacktrip.h | 21 +- src/gui/qjacktrip.qrc | 46 + src/gui/qjacktrip.ui | 17 + src/gui/qjacktrip_novs.qrc | 7 + src/gui/qjacktrip_novs.ui | 17 + src/gui/virtualstudio.cpp | 1223 +++++++++++++ src/gui/virtualstudio.h | 253 +++ src/gui/vs.qml | 122 ++ src/gui/vsQuickView.cpp | 47 + src/gui/vsQuickView.h | 55 + src/gui/vsServerInfo.cpp | 211 +++ src/gui/vsServerInfo.h | 137 ++ src/gui/wedge.svg | 51 + src/gui/wedge_inactive.svg | 51 + src/jacktrip_globals.h | 2 +- src/main.cpp | 74 +- win/CodeSignTool/CodeSignTool.sh | 1 + .../conf/code_sign_tool.properties | 4 + win/CodeSignTool/conf/log4j2.xml | 28 + win/build_installer.bat | 14 +- win/files.wxs | 1534 +++++++++++++++++ win/jacktrip.wxs | 12 +- 126 files changed, 8494 insertions(+), 419 deletions(-) rename macos/package/{link.sh => postinstall.sh} (53%) mode change 100644 => 100755 create mode 100644 releases/edge/mac-manifests.json create mode 100644 releases/edge/win-manifests.json create mode 100644 releases/stable/mac-manifests.json create mode 100644 releases/stable/win-manifests.json create mode 100644 src/dblsqd/feed.cpp create mode 100644 src/dblsqd/feed.h create mode 100644 src/dblsqd/release.cpp create mode 100644 src/dblsqd/release.h create mode 100644 src/dblsqd/semver.cpp create mode 100644 src/dblsqd/semver.h create mode 100644 src/dblsqd/update_dialog.cpp create mode 100644 src/dblsqd/update_dialog.h create mode 100644 src/dblsqd/update_dialog.ui create mode 100644 src/gui/Browse.qml create mode 100644 src/gui/Connected.qml create mode 100644 src/gui/FirstLaunch.qml create mode 100644 src/gui/JTOriginal.png create mode 100644 src/gui/JTVS.png create mode 100644 src/gui/Login.qml create mode 100644 src/gui/Poppins-Bold.ttf create mode 100644 src/gui/Poppins-Regular.ttf create mode 100644 src/gui/Settings.qml create mode 100644 src/gui/Setup.qml create mode 100644 src/gui/Studio.qml create mode 100644 src/gui/cog.svg create mode 100644 src/gui/ethernet.png create mode 100644 src/gui/flags/AE.svg create mode 100644 src/gui/flags/AU.svg create mode 100644 src/gui/flags/BE.svg create mode 100644 src/gui/flags/BR.svg create mode 100644 src/gui/flags/CA.svg create mode 100644 src/gui/flags/CH.svg create mode 100644 src/gui/flags/DE.svg create mode 100644 src/gui/flags/FR.svg create mode 100644 src/gui/flags/GB.svg create mode 100644 src/gui/flags/HK.svg create mode 100644 src/gui/flags/ID.svg create mode 100644 src/gui/flags/IT.svg create mode 100644 src/gui/flags/JP.svg create mode 100644 src/gui/flags/RO.svg create mode 100644 src/gui/flags/SE.svg create mode 100644 src/gui/flags/SG.svg create mode 100644 src/gui/flags/TW.svg create mode 100644 src/gui/flags/US.svg create mode 100644 src/gui/flags/ZA.svg create mode 100644 src/gui/headphones.svg create mode 100644 src/gui/jacktrip white.png create mode 100644 src/gui/jacktrip.png create mode 100644 src/gui/join.svg create mode 100644 src/gui/leave.svg create mode 100644 src/gui/logo.svg create mode 100644 src/gui/mic.svg create mode 100644 src/gui/private.svg create mode 100644 src/gui/public.svg create mode 100644 src/gui/qjacktrip_novs.qrc create mode 100644 src/gui/virtualstudio.cpp create mode 100644 src/gui/virtualstudio.h create mode 100644 src/gui/vs.qml create mode 100644 src/gui/vsQuickView.cpp create mode 100644 src/gui/vsQuickView.h create mode 100644 src/gui/vsServerInfo.cpp create mode 100644 src/gui/vsServerInfo.h create mode 100644 src/gui/wedge.svg create mode 100644 src/gui/wedge_inactive.svg create mode 100755 win/CodeSignTool/CodeSignTool.sh create mode 100644 win/CodeSignTool/conf/code_sign_tool.properties create mode 100644 win/CodeSignTool/conf/log4j2.xml diff --git a/.clang-format-ignore b/.clang-format-ignore index 8360478..9337953 100644 --- a/.clang-format-ignore +++ b/.clang-format-ignore @@ -1 +1,4 @@ externals/* +faust-src/faust2header.cpp +src/*dsp.h + diff --git a/CMakeLists.txt b/CMakeLists.txt index 40cae87..3fb5738 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,15 +1,18 @@ cmake_minimum_required(VERSION 3.12) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.13) +set(CMAKE_OSX_ARCHITECTURES arm64;x86_64) project(QJackTrip) set(nogui FALSE) set(rtaudio TRUE) set(weakjack TRUE) -add_compile_definitions(NO_JTVS) +set(novs FALSE) +add_compile_definitions(NO_UPDATER) add_compile_definitions(QT_OPENSOURCE) if (nogui) add_compile_definitions(NO_GUI) + set(novs TRUE) endif () if (rtaudio) @@ -21,6 +24,10 @@ if (weakjack) include_directories("externals/weakjack") endif() +if (novs) + add_compile_definitions(NO_VS) +endif () + if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") set (ENV{PKG_CONFIG_PATH} "/usr/local/lib/pkgconfig") elseif (${CMAKE_SYSTEM_NAME} MATCHES "Windows") @@ -43,7 +50,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin find_package(PkgConfig REQUIRED) pkg_check_modules(JACK REQUIRED IMPORTED_TARGET jack) if (weakjack) - # On mac, weakjack doesnt't find jack unless this is explicitly included. + # On mac, weakjack doesn't find jack unless this is explicitly included. include_directories(${JACK_INCLUDE_DIRS}) endif () if (rtaudio) @@ -51,6 +58,7 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin endif () endif () +set_property(SOURCE src/Regulator.h PROPERTY SKIP_AUTOGEN ON) # Find includes in corresponding build directories set(CMAKE_INCLUDE_CURRENT_DIR ON) # Instruct CMake to run moc automatically when needed. @@ -63,6 +71,10 @@ set(CMAKE_AUTORCC ON) # Find the QtWidgets library if (NOT nogui) find_package(Qt5Widgets CONFIG REQUIRED) + if (NOT novs) + find_package(Qt5Quick CONFIG REQUIRED) + find_package(Qt5NetworkAuth CONFIG REQUIRED) + endif () endif () find_package(Qt5Network CONFIG REQUIRED) @@ -110,8 +122,18 @@ if (NOT nogui) src/gui/about.cpp src/gui/messageDialog.cpp src/gui/textbuf.cpp - src/gui/qjacktrip.qrc ) + + if (NOT novs) + set (qjacktrip_SRC ${qjacktrip_SRC} + src/gui/virtualstudio.cpp + src/gui/vsServerInfo.cpp + src/gui/vsQuickView.cpp + src/gui/qjacktrip.qrc + ) + else () + set (qjacktrip_SRC ${qjacktrip_SRC} src/gui/qjacktrip_novs.qrc) + endif () if (${CMAKE_SYSTEM_NAME} MATCHES "Windows") set (qjacktrip_SRC ${qjacktrip_SRC} win/qjacktrip.rc) @@ -128,9 +150,12 @@ add_compile_definitions(WAIRTOHUB) add_executable(jacktrip ${qjacktrip_SRC}) # Set our libraries for our linker -set (qjacktrip_LIBS Qt5::Widgets) +set (qjacktrip_LIBS Qt5::Network) if (NOT nogui) - set (qjacktrip_LIBS ${qjacktrip_LIBS} Qt5::Network) + set (qjacktrip_LIBS ${qjacktrip_LIBS} Qt5::Widgets) + if (NOT novs) + set (qjacktrip_LIBS ${qjacktrip_LIBS} Qt5::Quick Qt5::NetworkAuth) + endif () endif () if (${CMAKE_SYSTEM_NAME} MATCHES "Linux" OR ${CMAKE_SYSTEM_NAME} MATCHES "Darwin") diff --git a/build b/build index 1f8d5ca..b4141be 100755 --- a/build +++ b/build @@ -11,13 +11,15 @@ RTAUDIO=0 NO_SYSTEM_RTAUDIO=0 PRO_FILE="../jacktrip.pro" HELP_STR="usage:\n -./build [noclean nojack rtaudio nogui static install [-config static]]\n\n +./build [noclean nojack rtaudio nogui novs static install [-config static]]\n\n options:\n noclean - do not run \"make clean\" first\n nojack - build without jack\n rtaudio - build with RtAudio\n no-system-rtaudio - use bundled RtAudio library even if it's available in the system\n nogui - build without the gui\n +novs - build without Virtual Studio support\n +noupdater - build without auto-update support\n static - build with static libraries\n weakjack - build with weak linking of jack libraries\n install - install jacktrip in system location (uses sudo)\n @@ -40,6 +42,14 @@ while [[ "$#" -gt 0 ]]; do echo "Building without the gui" CONFIG="-config nogui $CONFIG" ;; + novs) + echo "Building without Virtual Studio support" + CONFIG="-config novs $CONFIG" + ;; + noupdater) + echo "Building without auto-update support" + CONFIG="-config noupdater $CONFIG" + ;; static) echo "Building with static libraries" CONFIG="-config static $CONFIG" @@ -66,6 +76,9 @@ while [[ "$#" -gt 0 ]]; do fi done +echo "All build options:" +echo "$CONFIG" + # Check for Platform platform='unknown' unamestr=`uname` @@ -99,6 +112,8 @@ elif [[ $platform == 'macosx' ]]; then QSPEC=macx-clang-arm64 fi # if qmake is not in path, try homebrew qt5 + echo "path to qmake" + echo "$(which qmake)" if ! command -v $QCMD &> /dev/null; then # echo "qmake not found in \$PATH, searching for homebrew qt5" QT_PREFIX=`brew --prefix qt5` diff --git a/docs/Build/Linux.md b/docs/Build/Linux.md index 698c760..b722e21 100644 --- a/docs/Build/Linux.md +++ b/docs/Build/Linux.md @@ -16,7 +16,7 @@ Optional: ### Fedora ```sh -dnf install qt5-devel +dnf install qt5-qtbase-devel qt5-qtnetworkauth-devel qt5-qtquickcontrols2-devel qt5-qtsvg-devel dnf groupinstall "C Development Tools and Libraries" dnf groupinstall "Development Tools" dnf install "pkgconfig(jack)" alsa-lib-devel git help2man @@ -29,7 +29,7 @@ directory or use QtCreator to compile. ### Ubuntu and Debian/Raspbian ```sh apt install --no-install-recommends build-essential qt5-default autoconf automake libtool make libjack-jackd2-dev git help2man -apt install qjackctl +apt install qjackctl qt5-qmake qttools5-dev libqt5svg5-dev libqt5networkauth5-dev qtdeclarative5-dev qml-module-qtquick-controls apt install librtaudio-dev # if building with RtAudio ``` diff --git a/docs/Build/Mac.md b/docs/Build/Mac.md index 69c0428..898361f 100644 --- a/docs/Build/Mac.md +++ b/docs/Build/Mac.md @@ -72,7 +72,7 @@ To build using QtCreator: * Open jacktrip.pro using QtCreator * Choose a correctly configured Kit -QtCreator places the `jacktrip` executabe by default in a folder +QtCreator places the `jacktrip` executable by default in a folder with a name like `build-jacktrip-Desktop_x86_darwin_generic_mach_o_64bit-Release/`. ## Installation diff --git a/docs/Build/Meson_build.md b/docs/Build/Meson_build.md index 3ece861..5ea4037 100644 --- a/docs/Build/Meson_build.md +++ b/docs/Build/Meson_build.md @@ -20,7 +20,7 @@ find its documentation at [mesonbuild.com](https://mesonbuild.com/). === "MacOS" ```bash - brew install meson qt5 rt-audio help2man + brew install meson qt5 rtaudio help2man ``` You also need to install Jack, unless you want to disable jack support @@ -47,7 +47,7 @@ Meson shows you also the options of subprojects like RtAudio. ## Build Meson builds in a separate directory. It doesn't touch anything of your project. -This way you can have seperate debug and release build directories for example. +This way you can have separate debug and release build directories for example. Prepare your build directory: ```bash diff --git a/docs/changelog.yml b/docs/changelog.yml index d0d6023..be39c91 100644 --- a/docs/changelog.yml +++ b/docs/changelog.yml @@ -1,3 +1,12 @@ +- Version: "1.6.0" + Date: 2022-05-30 + Description: + - (added) Virtual Studio integration; previous GUI is now called "Classic Mode" + - (added) dblsqd for auto-updates + - (updated) buffer strategy 3 - multiple updates and fixes, still experimental + - (added) JackTrip Labs signing scripts + - (fixed) OpenSSL in the build script + - (updated) code cleanup and maintenance - Version: "1.5.3" Date: 2022-03-28 Description: diff --git a/faust-src/faust2header.cpp b/faust-src/faust2header.cpp index 8c7259f..6b3a647 100644 --- a/faust-src/faust2header.cpp +++ b/faust-src/faust2header.cpp @@ -14,9 +14,8 @@ //---------------------------------------------------------------------------- // FAUST Generated Code //---------------------------------------------------------------------------- -// clang-format off + <> - <> +<> - // clang-format on diff --git a/faust-src/zitarevdsp.dsp b/faust-src/zitarevdsp.dsp index b98a83c..d709c6d 100644 --- a/faust-src/zitarevdsp.dsp +++ b/faust-src/zitarevdsp.dsp @@ -4,6 +4,9 @@ import("stdfaust.lib"); process = zita_rev1; // same as dm.zita_rev1 but for wetness control and some defaults +//process = zita_rev1 : _,attach(cout); // Not using this solution yet, but it works +//cout = ffunction (int cout(), , ""); // dummy function to force #include in output + //----------------------------------`(dm.)zita_rev1`------------------------------ // Example GUI for `zita_rev1_stereo` (mostly following the Linux `zita-rev1` GUI). // diff --git a/jacktrip.pro b/jacktrip.pro index 0b2a5ee..106e37d 100644 --- a/jacktrip.pro +++ b/jacktrip.pro @@ -17,7 +17,7 @@ CONFIG(debug, debug|release) { } equals(QT_EDITION, "OpenSource") { - DEFINES += QT_OPENSOURCE + DEFINES += QT_OPENSOURCE } nogui { @@ -26,6 +26,17 @@ nogui { } else { QT += gui QT += widgets + novs { + DEFINES += NO_VS + } else { + QT += networkauth + QT += qml + QT += quick + QT += svg + } + noupdater { + DEFINES += NO_UPDATER + } } QT += network @@ -44,9 +55,6 @@ nojack { DEFINES += NO_JACK } -# for plugins -INCLUDEPATH += faust-src-lair/stk - !win32 { INCLUDEPATH+=/usr/local/include # wair needs stk, can be had from linux this way @@ -140,7 +148,6 @@ linux-g++-64 { message(Linux 64bit) } - win32 { message(Building on win32) #cc CONFIG += x86 console @@ -184,14 +191,11 @@ QMAKE_CLEAN += -r ./jacktrip ./jacktrip_debug ./release/* ./debug/* ./$${applica # isEmpty(PREFIX) will allow path to be changed during the command line # call to qmake, e.g. qmake PREFIX=/usr isEmpty(PREFIX) { - PREFIX = /usr/local + PREFIX = /usr/local } target.path = $$PREFIX/bin/ INSTALLS += target -# for plugins -INCLUDEPATH += faust-src-lair - # Input HEADERS += src/DataProtocol.h \ src/JackTrip.h \ @@ -223,20 +227,31 @@ HEADERS += src/DataProtocol.h \ #(Removed JackTripThread.h JackTripWorkerMessages.h NetKS.h TestRingBuffer.h ThreadPoolTest.h) !nojack { -HEADERS += src/JackAudioInterface.h \ - src/JMess.h \ - src/Patcher.h + HEADERS += src/JackAudioInterface.h \ + src/JMess.h \ + src/Patcher.h } !nogui { -HEADERS += src/gui/about.h \ - src/gui/messageDialog.h \ - src/gui/qjacktrip.h \ - src/gui/textbuf.h + HEADERS += src/gui/about.h \ + src/gui/messageDialog.h \ + src/gui/qjacktrip.h \ + src/gui/textbuf.h + !novs { + HEADERS += src/gui/virtualstudio.h \ + src/gui/vsServerInfo.h \ + src/gui/vsQuickView.h + } + !noupdater { + HEADERS += src/dblsqd/feed.h \ + src/dblsqd/release.h \ + src/dblsqd/semver.h \ + src/dblsqd/update_dialog.h + } } rtaudio|bundled_rtaudio { - HEADERS += src/RtAudioInterface.h + HEADERS += src/RtAudioInterface.h } SOURCES += src/DataProtocol.cpp \ @@ -262,16 +277,27 @@ SOURCES += src/DataProtocol.cpp \ #(Removed jacktrip_main.cpp jacktrip_tests.cpp JackTripThread.cpp ProcessPlugin.cpp) !nojack { -SOURCES += src/JackAudioInterface.cpp \ - src/JMess.cpp \ - src/Patcher.cpp + SOURCES += src/JackAudioInterface.cpp \ + src/JMess.cpp \ + src/Patcher.cpp } !nogui { -SOURCES += src/gui/messageDialog.cpp \ - src/gui/qjacktrip.cpp \ - src/gui/about.cpp \ - src/gui/textbuf.cpp + SOURCES += src/gui/messageDialog.cpp \ + src/gui/qjacktrip.cpp \ + src/gui/about.cpp \ + src/gui/textbuf.cpp + !novs { + SOURCES += src/gui/virtualstudio.cpp \ + src/gui/vsServerInfo.cpp \ + src/gui/vsQuickView.cpp + } + !noupdater { + SOURCES += src/dblsqd/feed.cpp \ + src/dblsqd/release.cpp \ + src/dblsqd/semver.cpp \ + src/dblsqd/update_dialog.cpp + } } !nogui { @@ -279,13 +305,19 @@ SOURCES += src/gui/messageDialog.cpp \ HEADERS += src/gui/NoNap.h OBJECTIVE_SOURCES += src/gui/NoNap.mm } - FORMS += src/gui/qjacktrip.ui src/gui/about.ui src/gui/messageDialog.ui - RESOURCES += src/gui/qjacktrip.qrc + novs { + RESOURCES += src/gui/qjacktrip_novs.qrc + } else { + RESOURCES += src/gui/qjacktrip.qrc + } + !noupdater { + FORMS += src/dblsqd/update_dialog.ui + } } rtaudio|bundled_rtaudio { - SOURCES += src/RtAudioInterface.cpp + SOURCES += src/RtAudioInterface.cpp } weakjack { diff --git a/linux/flatpak/org.jacktrip.JackTrip.Devel.yml b/linux/flatpak/org.jacktrip.JackTrip.Devel.yml index 8fe947c..ef0cb43 100644 --- a/linux/flatpak/org.jacktrip.JackTrip.Devel.yml +++ b/linux/flatpak/org.jacktrip.JackTrip.Devel.yml @@ -1,6 +1,6 @@ app-id: org.jacktrip.JackTrip.Devel runtime: org.kde.Platform -runtime-version: '5.15' +runtime-version: '5.15-21.08' sdk: org.kde.Sdk command: jacktrip finish-args: @@ -9,12 +9,12 @@ finish-args: - --socket=x11 # Wayland access # - --socket=wayland + # OpenGL + - --device=dri # Needs network access - --share=network # Pipewire/Jack - --filesystem=xdg-run/pipewire-0 - # For setting realtime priority for network thread? - - --socket=system-bus cleanup: - /lib/python3.8 - /share/man @@ -27,20 +27,20 @@ modules: - pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}" --prefix=${FLATPAK_DEST} "pyyaml" --no-build-isolation sources: - type: file - sha256: 68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 url: https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz + sha256: 68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 - name: python3-jinja2 buildsystem: simple cleanup: [ "*" ] build-commands: - pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}" --prefix=${FLATPAK_DEST} "jinja2" --no-build-isolation sources: - - sha256: 594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a - type: file - url: https://files.pythonhosted.org/packages/bf/10/ff66fea6d1788c458663a84d88787bae15d45daa16f6b3ef33322a51fc7e/MarkupSafe-2.0.1.tar.gz - - sha256: 077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 - type: file - url: https://files.pythonhosted.org/packages/20/9a/e5d9ec41927401e41aea8af6d16e78b5e612bca4699d417f646a9610a076/Jinja2-3.0.3-py3-none-any.whl + - type: file + url: https://files.pythonhosted.org/packages/1d/97/2288fe498044284f39ab8950703e88abbac2abbdf65524d576157af70556/MarkupSafe-2.1.1.tar.gz + sha256: 7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b + - type: file + url: https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl + sha256: 6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 - name: jacktrip buildsystem: meson config-opts: diff --git a/linux/flatpak/org.jacktrip.JackTrip.Devel.yml.j2 b/linux/flatpak/org.jacktrip.JackTrip.Devel.yml.j2 index 93c7f28..b2d211c 100644 --- a/linux/flatpak/org.jacktrip.JackTrip.Devel.yml.j2 +++ b/linux/flatpak/org.jacktrip.JackTrip.Devel.yml.j2 @@ -1,20 +1,20 @@ app-id: org.jacktrip.JackTrip.Devel runtime: org.kde.Platform -runtime-version: '5.15' +runtime-version: '5.15-21.08' sdk: org.kde.Sdk command: jacktrip finish-args: # X11 + XShm access - --share=ipc - --socket=x11 - # Wayland access (disabled because of missing window shadows) + # Wayland access # - --socket=wayland + # OpenGL + - --device=dri # Needs network access - --share=network # Pipewire/Jack - --filesystem=xdg-run/pipewire-0 - # For setting realtime priority for network thread? - - --socket=system-bus cleanup: - /lib/python3.8 - /share/man @@ -27,20 +27,20 @@ modules: - pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}" --prefix=${FLATPAK_DEST} "pyyaml" --no-build-isolation sources: - type: file - sha256: 68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 url: https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz + sha256: 68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 - name: python3-jinja2 buildsystem: simple cleanup: [ "*" ] build-commands: - pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}" --prefix=${FLATPAK_DEST} "jinja2" --no-build-isolation sources: - - sha256: 594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a - type: file - url: https://files.pythonhosted.org/packages/bf/10/ff66fea6d1788c458663a84d88787bae15d45daa16f6b3ef33322a51fc7e/MarkupSafe-2.0.1.tar.gz - - sha256: 077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 - type: file - url: https://files.pythonhosted.org/packages/20/9a/e5d9ec41927401e41aea8af6d16e78b5e612bca4699d417f646a9610a076/Jinja2-3.0.3-py3-none-any.whl + - type: file + url: https://files.pythonhosted.org/packages/1d/97/2288fe498044284f39ab8950703e88abbac2abbdf65524d576157af70556/MarkupSafe-2.1.1.tar.gz + sha256: 7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b + - type: file + url: https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl + sha256: 6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 - name: jacktrip buildsystem: meson config-opts: diff --git a/linux/flatpak/org.jacktrip.JackTrip.yml b/linux/flatpak/org.jacktrip.JackTrip.yml index eda8152..9bdfcc5 100644 --- a/linux/flatpak/org.jacktrip.JackTrip.yml +++ b/linux/flatpak/org.jacktrip.JackTrip.yml @@ -1,6 +1,6 @@ app-id: org.jacktrip.JackTrip runtime: org.kde.Platform -runtime-version: '5.15' +runtime-version: '5.15-21.08' sdk: org.kde.Sdk command: jacktrip finish-args: @@ -9,12 +9,12 @@ finish-args: - --socket=x11 # Wayland access # - --socket=wayland + # OpenGL + - --device=dri # Needs network access - --share=network # Pipewire/Jack - --filesystem=xdg-run/pipewire-0 - # For setting realtime priority for network thread? - - --socket=system-bus cleanup: - /lib/python3.8 - /share/man @@ -27,20 +27,20 @@ modules: - pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}" --prefix=${FLATPAK_DEST} "pyyaml" --no-build-isolation sources: - type: file - sha256: 68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 url: https://files.pythonhosted.org/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz + sha256: 68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 - name: python3-jinja2 buildsystem: simple cleanup: [ "*" ] build-commands: - pip3 install --verbose --exists-action=i --no-index --find-links="file://${PWD}" --prefix=${FLATPAK_DEST} "jinja2" --no-build-isolation sources: - - sha256: 594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a - type: file - url: https://files.pythonhosted.org/packages/bf/10/ff66fea6d1788c458663a84d88787bae15d45daa16f6b3ef33322a51fc7e/MarkupSafe-2.0.1.tar.gz - - sha256: 077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 - type: file - url: https://files.pythonhosted.org/packages/20/9a/e5d9ec41927401e41aea8af6d16e78b5e612bca4699d417f646a9610a076/Jinja2-3.0.3-py3-none-any.whl + - type: file + url: https://files.pythonhosted.org/packages/1d/97/2288fe498044284f39ab8950703e88abbac2abbdf65524d576157af70556/MarkupSafe-2.1.1.tar.gz + sha256: 7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b + - type: file + url: https://files.pythonhosted.org/packages/bc/c3/f068337a370801f372f2f8f6bad74a5c140f6fda3d9de154052708dd3c65/Jinja2-3.1.2-py3-none-any.whl + sha256: 6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61 - name: jacktrip buildsystem: meson sources: diff --git a/macos/JackTrip.app_template/Contents/Info.plist b/macos/JackTrip.app_template/Contents/Info.plist index 58cd87e..0ca37fd 100644 --- a/macos/JackTrip.app_template/Contents/Info.plist +++ b/macos/JackTrip.app_template/Contents/Info.plist @@ -27,7 +27,7 @@ MacOSX CFBundleVersion - 16 + %VERSION% DTCompiler com.apple.compilers.llvm.clang.1_0 DTPlatformBuild diff --git a/macos/assemble_app.sh b/macos/assemble_app.sh index aa7c4b4..6318bb7 100755 --- a/macos/assemble_app.sh +++ b/macos/assemble_app.sh @@ -47,7 +47,7 @@ while getopts ":inhc:d:u:p:a:b:" opt; do h) echo "JackTrip App Bundle assembly script." echo "Copyright (C) 2020-2021 Aaron Wyatt et al." - echo "Relased under the GNU GPLv3 License." + echo "Released under the GNU GPLv3 License." echo echo "Usage: ./assemble-app.sh [options] [appname] [bundlename]" echo @@ -114,10 +114,15 @@ if [ ! -z "$DYNAMIC_QT" ]; then exit 1 fi fi + VS=$(otool -L ../builddir/jacktrip | grep QtQml) + QMLDIR="" + if [ ! -z "VS" ]; then + QMLDIR=" -qmldir=../src/gui" + fi if [ ! -z "$CERTIFICATE" ]; then - $DEPLOY_CMD "$APPNAME.app" -codesign="$CERTIFICATE" + $DEPLOY_CMD "$APPNAME.app"$QMLDIR -codesign="$CERTIFICATE" else - $DEPLOY_CMD "$APPNAME.app" + $DEPLOY_CMD "$APPNAME.app"$QMLDIR fi fi diff --git a/macos/package/JackTrip.pkgproj_template b/macos/package/JackTrip.pkgproj_template index f5f2af4..18a6e56 100644 --- a/macos/package/JackTrip.pkgproj_template +++ b/macos/package/JackTrip.pkgproj_template @@ -42,7 +42,7 @@ BUNDLE_POSTINSTALL_PATH PATH - link.sh + postinstall.sh PATH_TYPE 1 diff --git a/macos/package/link.sh b/macos/package/postinstall.sh old mode 100644 new mode 100755 similarity index 53% rename from macos/package/link.sh rename to macos/package/postinstall.sh index 350da28..a668d3c --- a/macos/package/link.sh +++ b/macos/package/postinstall.sh @@ -1,5 +1,10 @@ #!/bin/sh +# Link jacktrip to app binary mkdir -p /usr/local/bin rm -f /usr/local/bin/jacktrip ln -s "$2"/Contents/MacOS/jacktrip /usr/local/bin/jacktrip + +# Open JackTrip on intaller finish +open -a /Applications/JackTrip.app +exit 0 diff --git a/meson.build b/meson.build index 4293f56..4c36da9 100644 --- a/meson.build +++ b/meson.build @@ -1,5 +1,5 @@ project('jacktrip', ['cpp','c'], - default_options: ['cpp_std=c++17','warning_level=2']) + default_options: ['cpp_std=c++17','warning_level=2','optimization=2']) if get_option('profile') == 'development' application_id = 'org.jacktrip.JackTrip.Devel' @@ -80,19 +80,60 @@ if get_option('nogui') == true defines += '-DNO_GUI' qt5_dep = dependency('qt5', modules: ['Core', 'Network'], include_type: 'system') else - qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets'], include_type: 'system') - src += ['src/gui/qjacktrip.cpp', + src += [ + 'src/gui/qjacktrip.cpp', 'src/gui/about.cpp', 'src/gui/messageDialog.cpp', - 'src/gui/textbuf.cpp'] - ui_h += ['src/gui/qjacktrip.ui', - 'src/gui/messageDialog.ui', - 'src/gui/about.ui'] - moc_h += ['src/gui/about.h', + 'src/gui/textbuf.cpp' + ] + moc_h += [ + 'src/gui/about.h', 'src/gui/qjacktrip.h', 'src/gui/messageDialog.h', - 'src/gui/textbuf.h'] - qres = ['src/gui/qjacktrip.qrc'] + 'src/gui/textbuf.h' + ] + ui_h += [ + 'src/gui/qjacktrip.ui', + 'src/gui/messageDialog.ui', + 'src/gui/about.ui' + ] + + if get_option('novs') == true + defines += '-DNO_VS' + qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets'], include_type: 'system') + qres = ['src/gui/qjacktrip_novs.qrc'] + else + src += [ + 'src/gui/virtualstudio.cpp', + 'src/gui/vsServerInfo.cpp', + 'src/gui/vsQuickView.cpp' + ] + moc_h += [ + 'src/gui/virtualstudio.h', + 'src/gui/vsServerInfo.h', + 'src/gui/vsQuickView.h' + ] + qt5_dep = dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets', 'Quick', 'Qml', 'Svg', 'NetworkAuth'], include_type: 'system') + qres = ['src/gui/qjacktrip.qrc'] + endif + + if get_option('noupdater') == true + defines += '-DNO_UPDATER' + else + src += [ + 'src/dblsqd/feed.cpp', + 'src/dblsqd/release.cpp', + 'src/dblsqd/semver.cpp', + 'src/dblsqd/update_dialog.cpp' + ] + moc_h += [ + 'src/dblsqd/feed.h', + 'src/dblsqd/release.h', + 'src/dblsqd/semver.h', + 'src/dblsqd/update_dialog.h' + ] + ui_h += ['src/dblsqd/update_dialog.ui'] + endif endif deps += qt5_dep diff --git a/meson_options.txt b/meson_options.txt index 6d93cd7..242ea80 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -3,4 +3,6 @@ option('rtaudio', type : 'feature', value : 'auto', description: 'Build with RtA option('jack', type : 'feature', value : 'auto', description: 'Build with JACK Backend') option('weakjack', type : 'boolean', value : 'false', description: 'Weak link JACK library') option('nogui', type : 'boolean', value : 'false', description: 'Build without graphical user interface') +option('novs', type : 'boolean', value : 'false', description: 'Build without Virtual Studio support') +option('noupdater', type : 'boolean', value : 'false', description: 'Build without auto-update support') option('profile', type: 'combo', choices: ['default', 'development'], value: 'default', description: 'Choose build profile / Sets desktop id accordingly') diff --git a/releases/edge/mac-manifests.json b/releases/edge/mac-manifests.json new file mode 100644 index 0000000..97de59d --- /dev/null +++ b/releases/edge/mac-manifests.json @@ -0,0 +1,65 @@ +{ + "app_name": "JackTrip", + "releases": [ + { + "version": "1.6.0", + "changelog": "Full integration with JackTrip Virtual Studio. Learn more here: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.0", + "download": { + "date": "2022-06-01T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0/JackTrip-v1.6.0-macOS-x64-installer.pkg", + "downloadSize": 11474299, + "sha256": "27259600ecd879106ebbf97754d72d6236075a049eafa0de6271d33f753f13e4" + } + }, + { + "version": "1.6.0-rc.5", + "changelog": "Release candidate 5 for 1.6.0", + "download": { + "date": "2022-05-30T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0-rc.5/JackTrip-v1.6.0-rc.5-macOS-x64-installer.pkg", + "downloadSize": 11474262, + "sha256": "8289530a8e6ef1f772776c7078679e2dac146f366cfc4e8c09e0ad16865fe274" + } + }, + { + "version": "1.6.0-rc.4", + "changelog": "Release candidate 4 for 1.6.0", + "download": { + "date": "2022-05-29T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0-rc.4/JackTrip-v1.6.0-rc.4-macOS-x64-installer.pkg", + "downloadSize": 11460550, + "sha256": "38d817f3e8cc61b707392ce74cee8ab46da9c8eb2086ea2b3f0c79496caf70a8" + } + }, + { + "version": "1.6.0-rc.3", + "changelog": "Release candidate 3 for 1.6.0", + "download": { + "date": "2022-05-27T00:00:00Z", + "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.0-rc.3-macOS-x64-installer.pkg", + "downloadSize": 11460230, + "sha256": "c9614964974d61c062d905f01c7d30ab04a697562ecfba6264392aebe7161051" + } + }, + { + "version": "1.6.0-rc.2", + "changelog": "Release candidate 2 for 1.6.0", + "download": { + "date": "2022-05-26T00:00:00Z", + "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.0-rc.2-macOS-x64-signed-installer.pkg", + "downloadSize": 11460155, + "sha256": "ad508680115f73036da3a5328ddf0841b86620406406e0ffaa4b982e24a27771" + } + }, + { + "version": "1.6.0-rc.1", + "changelog": "Release candidate 1 for 1.6.0", + "download": { + "date": "2022-05-23T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0-rc.1/JackTrip-v1.6.0-rc.1-macOS-x64-installer.pkg", + "downloadSize": 11076221, + "sha256": "071cda0ce59361e474a04db00beec41e92d2d823dab71e3fab179faf89f6fd7e" + } + } + ] +} \ No newline at end of file diff --git a/releases/edge/win-manifests.json b/releases/edge/win-manifests.json new file mode 100644 index 0000000..59b8186 --- /dev/null +++ b/releases/edge/win-manifests.json @@ -0,0 +1,65 @@ +{ + "app_name": "JackTrip", + "releases": [ + { + "version": "1.6.0", + "changelog": "Full integration with JackTrip Virtual Studio. Learn more here: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.0", + "download": { + "date": "2022-06-01T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0/JackTrip-v1.6.0-Windows-x64-installer.msi", + "downloadSize": 43364352, + "sha256": "9562ab654202bfc432e05caa3bd2bf1d0b52c50581b0a567f0546983fe46c078" + } + }, + { + "version": "1.6.0-rc.5", + "changelog": "Release candidate 5 for 1.6.0", + "download": { + "date": "2022-05-30T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0-rc.5/JackTrip-v1.6.0-rc.5-Windows-x64-installer.msi", + "downloadSize": 43364352, + "sha256": "d84e6e5d21cf31f5dd48e9dcc0c1a44fe7a37d977f94b6ff63d5e381745e5a44" + } + }, + { + "version": "1.6.0-rc.4", + "changelog": "Release candidate 4 for 1.6.0", + "download": { + "date": "2022-05-29T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0-rc.4/JackTrip-v1.6.0-rc.4-Windows-x64-installer.msi", + "downloadSize": 43126784, + "sha256": "cdb0ef906cf0d6047289838bf013b31a626cdd74dd4f75d6c1c4c3adbc9cd41d" + } + }, + { + "version": "1.6.0-rc.3", + "changelog": "Release candidate 3 for 1.6.0", + "download": { + "date": "2022-05-27T00:00:00Z", + "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.0-rc.3-Windows-x64-installer.msi", + "downloadSize": 43118592, + "sha256": "dceaf670a67cf1541007db82c5ce937b25370a7140e48192b94470f575fc4988" + } + }, + { + "version": "1.6.0-rc.2", + "changelog": "Release candidate 2 for 1.6.0", + "download": { + "date": "2022-05-26T00:00:00Z", + "url": "https://files.jacktrip.org/app-builds/JackTrip-v1.6.0-rc.2-Windows-x64-signed-installer.msi", + "downloadSize": 43114496, + "sha256": "b1a7adc8dc0fb47f59515790e8531dd10838d799bacb4b5653192ed621bca208" + } + }, + { + "version": "1.6.0-rc.1", + "changelog": "Release candidate 1 for 1.6.0", + "download": { + "date": "2022-05-23T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0-rc.1/JackTrip-v1.6.0-rc.1-Windows-x64-installer.msi", + "downloadSize": 43081728, + "sha256": "240f8b495ec5057228be922da80829a3718b474bafc2ba2d77750643abd1005c" + } + } + ] +} \ No newline at end of file diff --git a/releases/stable/mac-manifests.json b/releases/stable/mac-manifests.json new file mode 100644 index 0000000..0faec7c --- /dev/null +++ b/releases/stable/mac-manifests.json @@ -0,0 +1,15 @@ +{ + "app_name": "JackTrip", + "releases": [ + { + "version": "1.6.0", + "changelog": "Full integration with JackTrip Virtual Studio. Learn more here: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.0", + "download": { + "date": "2022-06-01T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0/JackTrip-v1.6.0-macOS-x64-installer.pkg", + "downloadSize": 11474299, + "sha256": "27259600ecd879106ebbf97754d72d6236075a049eafa0de6271d33f753f13e4" + } + } + ] +} \ No newline at end of file diff --git a/releases/stable/win-manifests.json b/releases/stable/win-manifests.json new file mode 100644 index 0000000..cb255c0 --- /dev/null +++ b/releases/stable/win-manifests.json @@ -0,0 +1,15 @@ +{ + "app_name": "JackTrip", + "releases": [ + { + "version": "1.6.0", + "changelog": "Full integration with JackTrip Virtual Studio. Learn more here: https://github.com/jacktrip/jacktrip/releases/tag/v1.6.0", + "download": { + "date": "2022-06-01T00:00:00Z", + "url": "https://github.com/jacktrip/jacktrip/releases/download/v1.6.0/JackTrip-v1.6.0-Windows-x64-installer.msi", + "downloadSize": 43364352, + "sha256": "9562ab654202bfc432e05caa3bd2bf1d0b52c50581b0a567f0546983fe46c078" + } + } + ] +} \ No newline at end of file diff --git a/scripts/hubMode/test_hub_mode_server_and_client.sh b/scripts/hubMode/test_hub_mode_server_and_client.sh index ff49980..3c3fc1d 100755 --- a/scripts/hubMode/test_hub_mode_server_and_client.sh +++ b/scripts/hubMode/test_hub_mode_server_and_client.sh @@ -48,7 +48,7 @@ if [ $JACKD != 0 ] # killall jackd if [ "$(ps -aux | grep -c jackd)" != 1 ]; then killall jackd; fi; # if jackd is or has been running with another driver -# much experimenation shows it literally takes this long +# much experimentation shows it literally takes this long sleep 17 # to flush old connections before starting the dummy driver diff --git a/src/AudioInterface.cpp b/src/AudioInterface.cpp index ba65477..a6dc55b 100644 --- a/src/AudioInterface.cpp +++ b/src/AudioInterface.cpp @@ -160,7 +160,7 @@ void AudioInterface::setup() mAudioInputPacket = new int8_t[size_audio_input]; mAudioOutputPacket = new int8_t[size_audio_output]; - // Initialize and asign memory for ProcessPlugins Buffers + // Initialize and assign memory for ProcessPlugins Buffers #ifdef WAIR // WAIR if (mNumNetRevChans) { mInProcessBuffer.resize(mNumNetRevChans); diff --git a/src/AudioInterface.h b/src/AudioInterface.h index db28f42..293e84a 100644 --- a/src/AudioInterface.h +++ b/src/AudioInterface.h @@ -109,9 +109,9 @@ class AudioInterface /** \brief Process callback. Subclass should call this callback after obtaining the in_buffer and out_buffer pointers. * \param in_buffer Array of input audio samplers for each channel. The user - * is reponsible to check that each channel has n_frames samplers + * is responsible to check that each channel has n_frames samplers * \param in_buffer Array of output audio samplers for each channel. The user - * is reponsible to check that each channel has n_frames samplers + * is responsible to check that each channel has n_frames samplers */ virtual void broadcastCallback(QVarLengthArray& mon_buffer, unsigned int n_frames); diff --git a/src/Auth.cpp b/src/Auth.cpp index 3cbd351..40eaeef 100644 --- a/src/Auth.cpp +++ b/src/Auth.cpp @@ -112,7 +112,7 @@ void Auth::loadAuthFile(const QString& filename) continue; } - // Check that our password hash is useable. + // Check that our password hash is usable. bool invalid = false; if (lineParts.at(1).startsWith(QLatin1String("$6$"))) { QStringList hashParts = lineParts.at(1).split(QStringLiteral("$")); @@ -185,7 +185,7 @@ bool Auth::checkTime(const QString& username) char Auth::char64(int value) { - // Returns a base 64 enconding using the following characters: + // Returns a base 64 encoding using the following characters: // ./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz if (value < 0 || value >= 64) { return 0; diff --git a/src/Compressor.cpp b/src/Compressor.cpp index 954a749..e0cb324 100644 --- a/src/Compressor.cpp +++ b/src/Compressor.cpp @@ -37,8 +37,6 @@ #include "Compressor.h" -#include - //******************************************************************************* void Compressor::compute(int nframes, float** inputs, float** outputs) { diff --git a/src/Compressor.h b/src/Compressor.h index 1208779..84fe58e 100644 --- a/src/Compressor.h +++ b/src/Compressor.h @@ -41,6 +41,7 @@ #ifndef __COMPRESSOR_H__ #define __COMPRESSOR_H__ +#include #include #include "CompressorPresets.h" diff --git a/src/DataProtocol.h b/src/DataProtocol.h index b01643f..38755b6 100644 --- a/src/DataProtocol.h +++ b/src/DataProtocol.h @@ -153,7 +153,7 @@ class DataProtocol : public QThread */ virtual void setPeerAddress(const char* peerHostOrIP) = 0; - /** \brief Set the peer incomming (receiving) port number + /** \brief Set the peer incoming (receiving) port number * \param port Port number * \todo implement here instead of in the subclass UDP */ diff --git a/src/JMess.cpp b/src/JMess.cpp index 85c6029..8517334 100644 --- a/src/JMess.cpp +++ b/src/JMess.cpp @@ -150,7 +150,7 @@ void JMess::connectTUB(int /*nChans*/) } // SC to jacktrip - tmp += 4; // increase tmp for port offest + tmp += 4; // increase tmp for port offset qDebug() << "connect " << serverAudio << HARDWIRED_AUDIO_PROCESS_ON_SERVER_OUT << tmp << "with " << client << ":send_" << l; diff --git a/src/JMess.h b/src/JMess.h index afb78c3..08b602b 100644 --- a/src/JMess.h +++ b/src/JMess.h @@ -50,7 +50,7 @@ const int Indent = 2; /*! \brief Class to save and load all jack client connections. * * Saves an XML file with all the current jack connections. This same file can - * be loaded to connect evrything again. The XML file can also be edited. + * be loaded to connect everything again. The XML file can also be edited. * * Has also an option to disconnect all the clients. */ diff --git a/src/JackAudioInterface.h b/src/JackAudioInterface.h index 44884f1..a3f6633 100644 --- a/src/JackAudioInterface.h +++ b/src/JackAudioInterface.h @@ -165,8 +165,8 @@ class JackAudioInterface : public AudioInterface * * jack_set_process_callback needs a static member function pointer. A normal * member function won't work because a this pointer is passed under the - * scenes. That's why we need to cast the member funcion processCallback to the static - * function wrapperProcessCallback. The callback is then set as:\n + * scenes. That's why we need to cast the member function processCallback to the + * static function wrapperProcessCallback. The callback is then set as:\n * jack_set_process_callback(mClient, JackAudioInterface::wrapperProcessCallback, * this) */ diff --git a/src/JackTrip.cpp b/src/JackTrip.cpp index e841731..a5475bb 100644 --- a/src/JackTrip.cpp +++ b/src/JackTrip.cpp @@ -168,8 +168,8 @@ void JackTrip::setupAudio( { // Check if mAudioInterface has already been created or not if (mAudioInterface - != NULL) { // if it has been created, disconnet it from JACK and delete it - cout << "WARINING: JackAudio interface was setup already:" << endl; + != NULL) { // if it has been created, disconnect it from JACK and delete it + cout << "WARNING: JackAudio interface was setup already:" << endl; cout << "It will be erased and setup again." << endl; cout << gPrintSeparator << endl; closeAudio(); @@ -377,15 +377,20 @@ void JackTrip::setupRingBuffers() new RingBuffer(audio_output_slot_size, mBufferQueueLength); mPacketHeader->setBufferRequiresSameSettings(true); } else if (mBufferStrategy == 3) { - qDebug() << "experimental buffer strategy 3 -- regulator with PLC"; - mSendRingBuffer = - new RingBuffer(audio_input_slot_size, gDefaultOutputQueueLength); + cout << "Using experimental buffer strategy " << mBufferStrategy + << "-- Regulator with PLC" << endl; + mReceiveRingBuffer = - new Regulator(mSampleRate, mNumAudioChansOut, mAudioBitResolution, - mAudioBufferSize, mBufferQueueLength); + new Regulator(mNumAudioChansOut, mAudioBitResolution, mAudioBufferSize, + mBufferQueueLength, mBroadcastQueueLength); // bufStrategy 3, mBufferQueueLength is in integer msec not packets mPacketHeader->setBufferRequiresSameSettings(false); // = asym is default + + if (0 < mBroadcastQueueLength) { + mAudioInterface->enableBroadcastOutput(); + } + } else { cout << "Using JitterBuffer strategy " << mBufferStrategy << endl; if (0 > mBufferQueueLength) { @@ -522,7 +527,7 @@ void JackTrip::startProcess( "clientPingToServerStart" << std::endl; if (clientPingToServerStart() - == -1) { // if error on server start (-1) we return inmediatly + == -1) { // if error on server start (-1) we return immediately stop(QStringLiteral( "Peer Address has to be set if you run in CLIENTTOPINGSERVER mode")); return; @@ -536,7 +541,7 @@ void JackTrip::startProcess( << " JackTrip:startProcess case SERVERPINGSERVER before serverStart" << std::endl; if (serverStart(true) - == -1) { // if error on server start (-1) we return inmediatly + == -1) { // if error on server start (-1) we return immediately stop(); return; } @@ -651,8 +656,7 @@ void JackTrip::onStatTimer() << now.toLocal8Bit().constData() << " " << getPeerAddress().toLocal8Bit().constData() << " send: " << send_io_stat.underruns << "/" << send_io_stat.overflows - << " Pull underruns: " - << recv_io_stat.underruns // pullStat->lastPlcUnderruns; + << " Glitches: " << recv_io_stat.underruns // pullStat->lastPlcUnderruns; #define INVFLOATFACTOR 0.001 << "\nPUSH -- SD avg/last: " << setw(5) << INVFLOATFACTOR * recv_io_stat.overflows // pushStat->longTermStdDev; @@ -679,8 +683,9 @@ void JackTrip::onStatTimer() // << "/" << recv_io_stat.overflows << " prot: " << // pkt_stat.lost << "/" // << pkt_stat.outOfOrder << "/" << pkt_stat.revived - << " \n tot: " - << pkt_stat.tot + << " \n tot: " << pkt_stat.tot << " \t tol: " << setw(5) + << INVFLOATFACTOR * recv_io_stat.autoq_corr << " \t dsp (last): " << setw(5) + << INVFLOATFACTOR * recv_io_stat.autoq_rate // << " sync: " << recv_io_stat.level << "/" // << recv_io_stat.buf_inc_underrun << "/" // << recv_io_stat.buf_inc_compensate << "/" @@ -826,7 +831,7 @@ void JackTrip::receivedDataTCP() mTcpClient.close(); // Close the socket // cout << "TCP Socket Closed!" << endl; - // If we sent authentication data, check if our authentication attempt was succesfull + // If we sent authentication data, check if our authentication attempt was successful if (mUseAuth && udp_port > 65535) { QString error_message; if (udp_port == Auth::WRONGCREDS) { @@ -854,7 +859,7 @@ void JackTrip::receivedDataTCP() } if (gVerboseFlag) - cout << "Connection Succesfull!" << endl; + cout << "Connection Successful!" << endl; // Set with the received UDP port // ------------------------------ @@ -967,7 +972,7 @@ void JackTrip::receivedDataUDP() // We reply to the same port the peer sent the packets from // This way we can go through NAT // Because of the NAT traversal scheme, the portn need to be - // "symetric", e.g.: + // "symmetric", e.g.: // from Client to Server : src = 4474, dest = 4464 // from Server to Client : src = 4464, dest = 4474 // no -- all are the same -- 4464 @@ -1116,6 +1121,7 @@ int JackTrip::serverStart(bool timeout, int udpTimeout) // udpTimeout unused mEndTime = udpTimeout; } mTimeoutTimer.setInterval(mSleepTime); + mTimeoutTimer.disconnect(); connect(&mTimeoutTimer, &QTimer::timeout, this, &JackTrip::udpTimerTick); mTimeoutTimer.start(); } @@ -1210,6 +1216,7 @@ int JackTrip::clientPingToServerStart() mElapsedTime = 0; mEndTime = 5000; // Timeout after 5 seconds. mTimeoutTimer.setInterval(mSleepTime); + mTimeoutTimer.disconnect(); connect(&mTimeoutTimer, &QTimer::timeout, this, &JackTrip::tcpTimerTick); mTimeoutTimer.start(); } @@ -1297,7 +1304,7 @@ active address local_addr.sin_port = htons(bind_port); //set bind port ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); #endif #if defined ( __APPLE__ ) - // This option is not avialable on Linux, and without it MAC OS X + // This option is not available on Linux, and without it MAC OS X // has problems rebinding a socket ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); #endif diff --git a/src/JackTrip.h b/src/JackTrip.h index 0c2ec25..fa877d0 100644 --- a/src/JackTrip.h +++ b/src/JackTrip.h @@ -399,6 +399,12 @@ class JackTrip : public QObject { return mReceiveRingBuffer->insertSlotNonBlocking(ptrToSlot, len, lostLen); } + virtual bool writeAudioBufferRegulator(const int8_t* ptrToSlot, int len, int seq, + int lostLen) + { + return mReceiveRingBuffer->insertSlotNonBlockingRegulator(ptrToSlot, len, seq, + lostLen); + } uint32_t getBufferSizeInSamples() const { return mAudioBufferSize; /*return mAudioInterface->getBufferSizeInSamples();*/ @@ -597,8 +603,8 @@ class JackTrip : public QObject /// \brief Starts for the CLIENT mode void clientStart(); /// \brief Starts for the SERVER mode - /// \param timout Set the server to timeout after 2 seconds if no client connections - /// are received. Usefull for the multithreaded server \return 0 on success, -1 on + /// \param timeout Set the server to timeout after 2 seconds if no client connections + /// are received. Useful for the multithreaded server \return 0 on success, -1 on /// error int serverStart(bool timeout = false, int udpTimeout = gTimeOutMultiThreadedServer); /// \brief Stats for the Client to Ping Server diff --git a/src/JackTripWorker.h b/src/JackTripWorker.h index 18a8127..7ce1d9e 100644 --- a/src/JackTripWorker.h +++ b/src/JackTripWorker.h @@ -152,10 +152,10 @@ class JackTripWorker : public QObject UdpHubListener* mUdpHubListener; ///< Hub Listener Socket // QHostAddress mClientAddress; ///< Client Address QString mClientAddress; - uint16_t mServerPort; ///< Server Ephemeral Incomming Port to use with Client + uint16_t mServerPort; ///< Server Ephemeral Incoming Port to use with Client bool m_connectDefaultAudioPorts = false; - /// Client Outgoing Port. By convention, the receving port will be mClientPort + /// Client Outgoing Port. By convention, the receiving port will be mClientPort /// -1 uint16_t mClientPort; diff --git a/src/Limiter.cpp b/src/Limiter.cpp index 80213f7..b23b5f0 100644 --- a/src/Limiter.cpp +++ b/src/Limiter.cpp @@ -38,8 +38,6 @@ #include "Limiter.h" -#include - #include "jacktrip_types.h" //******************************************************************************* diff --git a/src/Limiter.h b/src/Limiter.h index f311308..fea43bd 100644 --- a/src/Limiter.h +++ b/src/Limiter.h @@ -49,6 +49,7 @@ #endif #include +#include #include #include "ProcessPlugin.h" diff --git a/src/PacketHeader.cpp b/src/PacketHeader.cpp index cbd3bdf..23011e7 100644 --- a/src/PacketHeader.cpp +++ b/src/PacketHeader.cpp @@ -297,7 +297,7 @@ void JamLinkHeader::fillHeaderCommonFromAudio() // one channel" // << endl; // std::exit(1); - // std::cerr << "WARINING: JamLink only support ONE channel. Run JackTrip using + // std::cerr << "WARNING: JamLink only support ONE channel. Run JackTrip using // only one channel" << endl; throw std::logic_error("JamLink only support ONE // channel. Run JackTrip using only one channel"); emit signalError(QStringLiteral( @@ -307,7 +307,7 @@ void JamLinkHeader::fillHeaderCommonFromAudio() // Sampling Rate int rate_type = mJackTrip->getSampleRateType(); if (rate_type != AudioInterface::SR48) { - // std::cerr << "WARINING: JamLink only support 48kHz for communication with + // std::cerr << "WARNING: JamLink only support 48kHz for communication with // JackTrip at the moment." << endl; throw std::logic_error("ERROR: JamLink only // support 48kHz for communication with JackTrip at the moment."); emit signalError( @@ -318,7 +318,7 @@ void JamLinkHeader::fillHeaderCommonFromAudio() // Check Buffer Size int buf_size = mJackTrip->getBufferSizeInSamples(); if (buf_size != 64) { - // std::cerr << "WARINING: JamLink only support 64 buffer size for communication + // std::cerr << "WARNING: JamLink only support 64 buffer size for communication // with JackTrip at the moment." << endl; throw std::logic_error("ERROR: JamLink // only support 64 buffer size for communication with JackTrip at the moment."); emit signalError(QStringLiteral( diff --git a/src/PacketHeader.h b/src/PacketHeader.h index 30e9480..c31b4ba 100644 --- a/src/PacketHeader.h +++ b/src/PacketHeader.h @@ -48,7 +48,7 @@ #include "jacktrip_types.h" class JackTrip; // Forward Declaration -/// \brief Abstract Header Struct, Header Stucts should subclass it +/// \brief Abstract Header Struct, Header Structs should subclass it struct HeaderStruct { }; diff --git a/src/Regulator.cpp b/src/Regulator.cpp index a9c8e51..24a6c7d 100644 --- a/src/Regulator.cpp +++ b/src/Regulator.cpp @@ -35,30 +35,47 @@ * \date May-Sep 2021 */ -// EXPERIMENTAL for testing in JackTrip v1.5.0 -// requires server and client have same FPP -// runs ok from FPP 16 up to 1024 -// number of in / out channels should be the same -// mono, stereo and -n3 tested fine - -// ./jacktrip -S --udprt -p1 --bufstrategy 3 -I 1 -q10 -// PIPEWIRE_LATENCY=32/48000 ./jacktrip -C --udprt --bufstrategy 3 -I 1 -q4 - +// EXPERIMENTAL for testing in JackTrip v1.5. +// server and client can have different FPP (tested from FPP 16 to 1024) +// stress tested by repeatedly starting & stopping across range of FPP's +// server and client can have different in / out channel count +// large FPP, for example 512, should not be run with --udprt as PLC audio callback +// completion gets delayed auto mode -- use -q auto3 or for manual setting of initial +// mMsecTolerance -- use -q auto gathers data for 6 sec and then goes full auto + +// example WAN test +// ./jacktrip -S --udprt -p1 --bufstrategy 3 -q auto +// PIPEWIRE_LATENCY=32/48000 ./jacktrip -C --udprt --bufstrategy 3 -q auto + +// (mono : mono : mono) +// ./jacktrip -S -p1 --bufstrategy 3 -q auto3 -u --receivechannels 1 --sendchannels 1 +// --udprt -I 1 +// ./jacktrip -C --receivechannels 1 -u --sendchannels 1 --bufstrategy 3 -q auto3 +// -I 1 --udprt + +// latest (mono <: stereo : stereo) +// ./jacktrip -S -p1 --bufstrategy 3 -q auto3 -u --receivechannels 1 --sendchannels 2 +// --udprt -I 1 +// ./jacktrip -C --receivechannels 2 -u --sendchannels 1 --bufstrategy 3 -q auto3 +// -I 1 --udprt + +// example WAN test // at 48000 / 32 = 2.667 ms total roundtrip latency -// local loopback test with 4 terminals running and the following jmess file +// local loopback test with 4 terminals running and a jmess file // jacktrip -S --udprt --nojackportsconnect -q1 --bufstrategy 3 // jacktrip -C localhost --udprt --nojackportsconnect -q1 --bufstrategy 3 -// use jack_iodelay -// use jmess -s delay.xml and jmess -c delay.xml // tested outgoing loss impairments with (replace lo with relevant network interface) -// sudo tc qdisc add dev lo root netem loss 2% -// sudo tc qdisc del dev lo root netem loss 2% +// sudo tc qdisc add dev lo root netem loss 5% +// sudo tc qdisc del dev lo root netem loss 5% +// or very revealing +// sudo tc qdisc add dev lo root netem loss 20% +// sudo tc qdisc del dev lo root netem loss 20% // tested jitter impairments with -// for wifi +// wifi simulation // sudo tc qdisc add dev lo root netem slot distribution pareto 0.1ms 3.0ms // sudo tc qdisc del dev lo root netem slot distribution pareto 0.1ms 3.0ms -// for wired cmn9 +// ugly wired simulation // sudo tc qdisc add dev lo root netem slot distribution pareto 0.2ms 0.3ms // sudo tc qdisc del dev lo root netem slot distribution pareto 0.2ms 0.3ms @@ -67,30 +84,49 @@ #include #include +#include "JitterBuffer.h" #include "jacktrip_globals.h" using std::cout; using std::endl; using std::setw; -// constants... tested for now -constexpr int HIST = 6; // at FPP32 +// constants... +constexpr int HIST = 4; // for mono at FPP 16-128, see below for > mono, > 128 constexpr int ModSeqNumInit = 256; // bounds on seqnums, 65536 is max in packet header constexpr int NumSlotsMax = 128; // mNumSlots looped for recent arrivals constexpr int LostWindowMax = 32; // mLostWindow looped for recent arrivals +constexpr double DefaultAutoHeadroom = + 3.0; // msec padding for auto adjusting mMsecTolerance +constexpr double AutoMax = 250.0; // msec bounds on insane IPI, like ethernet unplugged +constexpr double AutoInitDur = 6000.0; // kick in auto after this many msec +constexpr double AutoInitValFactor = + 0.5; // scale for initial mMsecTolerance during init phase if unspecified +// tweak +constexpr int WindowDivisor = 8; // for faster auto tracking +constexpr int MaxFPP = 1024; // tested up to this FPP //******************************************************************************* -Regulator::Regulator(int sample_rate, int channels, int bit_res, int FPP, int qLen) +Regulator::Regulator(int rcvChannels, int bit_res, int FPP, int qLen, int bqLen) : RingBuffer(0, 0) - , mNumChannels(channels) + , mNumChannels(rcvChannels) , mAudioBitRes(bit_res) , mFPP(FPP) - , mSampleRate(sample_rate) - , mMsecTolerance((double)qLen) + , mMsecTolerance((double)qLen) // handle non-auto mode, expects positive qLen , mAuto(false) + , m_b_BroadcastQueueLength(bqLen) { - if (mMsecTolerance < 0.0) { // handle, for example, CLI -q auto15 or -q auto - mAuto = true; - mMsecTolerance *= -1.0; - }; + // catch settings that are compute bound using long HIST + // hub client rcvChannels is set from client's settings parameters + // hub server rcvChannels is set from connecting client, not from hub parameters + // if (mNumChannels > MaxChans) { + // std::cerr << "*** Regulator.cpp: receive channels = " << mNumChannels + // << " larger than max channels = " << MaxChans << "\n"; + // exit(1); + // } + if (mFPP > MaxFPP) { + std::cerr << "*** Regulator.cpp: local FPP = " << mFPP + << " larger than max FPP = " << MaxFPP << "\n"; + exit(1); + } switch (mAudioBitRes) { // int from JitterBuffer to AudioInterface enum case 1: mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT8; @@ -105,13 +141,18 @@ Regulator::Regulator(int sample_rate, int channels, int bit_res, int FPP, int qL mBitResolutionMode = AudioInterface::audioBitResolutionT::BIT32; break; } - mHist = HIST * 32; // samples, from original settings - double histFloat = mHist / (double)mFPP; // packets for other FPP - mHist = (int)histFloat; - if (mHist < 2) - mHist = 2; // min packets for prediction, needs at least 2 - else if (mHist > 6) - mHist = 6; // max packets, keep a lid on CPU load + mHist = HIST; // HIST (default) is 4 + // as FPP decreases the rate of PLC triggers potentially goes up + // and load increases so don't use an inverse relation + + // crossfaded prediction is a full packet ahead of predicted + // packet, so the size of mPrediction needs to account for 2 full packets (2*FPP) + // but trainSamps = (HIST * FPP) and mPrediction.resize(trainSamps - 1, 0.0) so if + // hist = 2, then it exceeds the size + + if (((mNumChannels > 1) && (mFPP > 64)) || (mFPP > 128)) + mHist = 3; // min packets for prediction, needs at least 3 + if (gVerboseFlag) cout << "mHist = " << mHist << " at " << mFPP << "\n"; mBytes = mFPP * mNumChannels * mBitResolutionMode; @@ -124,10 +165,7 @@ Regulator::Regulator(int sample_rate, int channels, int bit_res, int FPP, int qL mFadeDown[i] = 1.0 - mFadeUp[i]; } mLastWasGlitch = false; - mPacketDurMsec = 1000.0 * (double)mFPP / (double)mSampleRate; - if (mMsecTolerance < mPacketDurMsec) - mMsecTolerance = mPacketDurMsec; // absolute minimum - mNumSlots = NumSlotsMax; //((int)ceil(mMsecTolerance / mPacketDurMsec)) + PADSLOTS; + mNumSlots = NumSlotsMax; for (int i = 0; i < mNumSlots; i++) { int8_t* tmp = new int8_t[mBytes]; @@ -143,8 +181,6 @@ Regulator::Regulator(int sample_rate, int channels, int bit_res, int FPP, int qL memcpy(mZeros, mXfrBuffer, mBytes); mAssembledPacket = new int8_t[mBytes]; // for asym memcpy(mAssembledPacket, mXfrBuffer, mBytes); - pushStat = new StdDev(&mIncomingTimer, (int)(floor(48000.0 / (double)mFPP)), 1); - pullStat = new StdDev(&mIncomingTimer, (int)(floor(48000.0 / (double)mFPP)), 2); mLastLostCount = 0; // for stats mIncomingTimer.start(); mLastSeqNumIn = -1; @@ -156,23 +192,27 @@ Regulator::Regulator(int sample_rate, int channels, int bit_res, int FPP, int qL mModSeqNum = mNumSlots * 2; mFPPratioNumerator = 1; mFPPratioDenominator = 1; - mPartialPacketCnt = 0; mFPPratioIsSet = false; mBytesPeerPacket = mBytes; -#ifdef GUIBS3 - // hg for GUI - hg = new HerlperGUI(qApp->activeWindow()); - connect(hg, SIGNAL(moved(double)), this, SLOT(changeGlobal(double))); - connect(hg, SIGNAL(moved_2(int)), this, SLOT(changeGlobal_2(int))); - connect(hg, SIGNAL(moved_3(int)), this, SLOT(changeGlobal_3(int))); -#endif + mAssemblyCnt = 0; + mModCycle = 1; + mModSeqNumPeer = 1; + mPeerFPP = mFPP; // use local until first packet arrives + mAutoHeadroom = DefaultAutoHeadroom; + mFPPdurMsec = 1000.0 * mFPP / 48000.0; changeGlobal_3(LostWindowMax); changeGlobal_2(NumSlotsMax); // need hg if running GUI - changeGlobal((double)qLen); + if (m_b_BroadcastQueueLength) { + m_b_ReceiveRingBuffer = new JitterBuffer( + mFPP, qLen, 48000, 1, m_b_BroadcastQueueLength, mNumChannels, mAudioBitRes); + qDebug() << "Broadcast started in Regulator with packet queue of" + << m_b_BroadcastQueueLength; + // have not implemented the mJackTrip->queueLengthChanged functionality + } } void Regulator::changeGlobal(double x) -{ // mMsecTolerance +{ mMsecTolerance = x; printParams(); } @@ -194,75 +234,112 @@ void Regulator::changeGlobal_3(int x) printParams(); } -void Regulator::printParams() -{ -// qDebug() << "mMsecTolerance" << mMsecTolerance << "mNumSlots" << mNumSlots -// << "mModSeqNum" << mModSeqNum << "mLostWindow" << mLostWindow; -#ifdef GUIBS3 - updateGUI((int)mMsecTolerance, mNumSlots); -#endif +void Regulator::printParams(){ + // qDebug() << "mMsecTolerance" << mMsecTolerance << "mNumSlots" << mNumSlots + // << "mModSeqNum" << mModSeqNum << "mLostWindow" << mLostWindow; }; -#ifdef GUIBS3 -void Regulator::updateGUI(double msTol, int nSlots) -{ - hg->updateDisplay(msTol, nSlots, 0); // need to remove last param -} -#endif - Regulator::~Regulator() { - delete mXfrBuffer; - delete mZeros; + delete[] mXfrBuffer; + delete[] mZeros; + delete[] mAssembledPacket; + delete pushStat; + delete pullStat; for (int i = 0; i < mNumChannels; i++) delete mChanData[i]; + for (auto& slot : mSlots) { + delete[] slot; + }; + if (m_b_BroadcastQueueLength) + delete m_b_ReceiveRingBuffer; } -void Regulator::setFPPratio(int len) +void Regulator::setFPPratio() { - int peerFPP = len / (mNumChannels * mBitResolutionMode); - if (peerFPP != mFPP) { - if (peerFPP > mFPP) - mFPPratioDenominator = peerFPP / mFPP; + if (mPeerFPP != mFPP) { + if (mPeerFPP > mFPP) + mFPPratioDenominator = mPeerFPP / mFPP; else - mFPPratioNumerator = mFPP / peerFPP; - qDebug() << "peerBuffers / localBuffers" << mFPPratioNumerator << " / " - << mFPPratioDenominator; + mFPPratioNumerator = mFPP / mPeerFPP; + // qDebug() << "peerBuffers / localBuffers" << mFPPratioNumerator << " / " + // << mFPPratioDenominator; } - if (mFPPratioNumerator > 1) + if (mFPPratioNumerator > 1) { mBytesPeerPacket = mBytes / mFPPratioNumerator; - mFPPratioIsSet = true; + mModCycle = mFPPratioNumerator - 1; + mModSeqNumPeer = mModSeqNum * mFPPratioNumerator; + } else if (mFPPratioDenominator > 1) { + mModSeqNumPeer = mModSeqNum / mFPPratioDenominator; + } } //******************************************************************************* void Regulator::shimFPP(const int8_t* buf, int len, int seq_num) { if (seq_num != -1) { - if (!mFPPratioIsSet) - setFPPratio(len); - if (mFPPratioNumerator > 1) { // 2/1, 4/1 peer FPP is lower - int modSeqNumPeer = mModSeqNum * mFPPratioNumerator; - seq_num %= modSeqNumPeer; - // qDebug() << seq_num << seq_num / mFPPratioNumerator << - // mPartialPacketCnt; - seq_num /= mFPPratioNumerator; - int tmp = (mPartialPacketCnt % mFPPratioNumerator) * mBytesPeerPacket; - memcpy(&mAssembledPacket[tmp], buf, mBytesPeerPacket); - if ((mPartialPacketCnt % mFPPratioNumerator) == (mFPPratioNumerator - 1)) - pushPacket(mAssembledPacket, seq_num); - mPartialPacketCnt++; - } else if (mFPPratioDenominator > 1) { // 1/2, 1/4 peer FPP is higher - int modSeqNumPeer = mModSeqNum / mFPPratioDenominator; - seq_num %= modSeqNumPeer; - seq_num *= mFPPratioDenominator; - for (int i = 0; i < mFPPratioDenominator; i++) { - int tmp = i * mBytes; - memcpy(mAssembledPacket, &buf[tmp], mBytes); - pushPacket(mAssembledPacket, seq_num); - seq_num++; - } - } else + if (!mFPPratioIsSet) { // first peer packet + mPeerFPP = len / (mNumChannels * mBitResolutionMode); + // bufstrategy 1 autoq mode overloads qLen with negative val + // creates this ugly code + if (mMsecTolerance < 0) { // handle -q auto or, for example, -q auto10 + mAuto = true; + // default is -500 from bufstrategy 1 autoq mode + // tweak + if (mMsecTolerance != -500.0) { + // use it to set headroom + mAutoHeadroom = -mMsecTolerance; + qDebug() << "PLC is in auto mode and has been set with" + << mAutoHeadroom << "ms headroom"; + if (mAutoHeadroom > 50.0) + qDebug() << "That's a very large value and should be less than, " + "for example, 50ms"; + } + // found an interesting relationship between mPeerFPP and initial + // mMsecTolerance mPeerFPP*0.5 is pretty good though that's an oddball + // conversion of bufsize directly to msec + mMsecTolerance = (mPeerFPP * AutoInitValFactor); + }; + setFPPratio(); + // number of stats tick calls per sec depends on FPP + int maxFPP = (mPeerFPP > mFPP) ? mPeerFPP : mFPP; + pushStat = + new StdDev(1, &mIncomingTimer, (int)(floor(48000.0 / (double)maxFPP))); + pullStat = + new StdDev(2, &mIncomingTimer, (int)(floor(48000.0 / (double)mFPP))); + mFPPratioIsSet = true; + } + if (mFPPratioNumerator == mFPPratioDenominator) { pushPacket(buf, seq_num); + } else { + seq_num %= mModSeqNumPeer; + if (mFPPratioNumerator > 1) { // 2/1, 4/1 peer FPP is lower, , (local/peer)/1 + int tmp = (seq_num % mFPPratioNumerator) * mBytesPeerPacket; + memcpy(&mAssembledPacket[tmp], buf, mBytesPeerPacket); + if ((seq_num % mFPPratioNumerator) == mModCycle) { + if (mAssemblyCnt == mModCycle) + pushPacket(mAssembledPacket, seq_num / mFPPratioNumerator); + // else + // qDebug() << "incomplete due to lost packet"; + mAssemblyCnt = 0; + } else + mAssemblyCnt++; + } else if (mFPPratioDenominator + > 1) { // 1/2, 1/4 peer FPP is higher, 1/(peer/local) + seq_num *= mFPPratioDenominator; + for (int i = 0; i < mFPPratioDenominator; i++) { + int tmp = i * mBytes; + memcpy(mAssembledPacket, &buf[tmp], mBytes); + pushPacket(mAssembledPacket, seq_num); + seq_num++; + } + } + } + pushStat->tick(); + double adjustAuto = pushStat->calcAuto(mAutoHeadroom, mFPPdurMsec); + // qDebug() << adjustAuto; + if (mAuto && (pushStat->lastTime > AutoInitDur)) + mMsecTolerance = adjustAuto; } }; @@ -270,20 +347,13 @@ void Regulator::shimFPP(const int8_t* buf, int len, int seq_num) void Regulator::pushPacket(const int8_t* buf, int seq_num) { QMutexLocker locker(&mMutex); - // qDebug() << "\t" << seq_num; seq_num %= mModSeqNum; - // if (seq_num==0) return; // if (seq_num==1) return; // impose regular loss + // if (seq_num==0) return; // impose regular loss mIncomingTiming[seq_num] = mMsecTolerance + (double)mIncomingTimer.nsecsElapsed() / 1000000.0; mLastSeqNumIn = seq_num; if (mLastSeqNumIn != -1) memcpy(mSlots[mLastSeqNumIn % mNumSlots], buf, mBytes); - double nowMS = pushStat->tick(); - if (mAuto && (nowMS > 2000.0)) { - double tmp = pushStat->longTermStdDev + pushStat->longTermMax; - tmp += 2.0; // 2 ms -- kind of a guess - changeGlobal(tmp); - } }; //******************************************************************************* @@ -291,7 +361,7 @@ void Regulator::pullPacket(int8_t* buf) { QMutexLocker locker(&mMutex); mSkip = 0; - if (mLastSeqNumIn == -1) { + if ((mLastSeqNumIn == -1) || (!mFPPratioIsSet)) { goto ZERO_OUTPUT; } else { mLastSeqNumOut++; @@ -316,16 +386,19 @@ void Regulator::pullPacket(int8_t* buf) } PACKETOK : { - if (mSkip) + if (mSkip) { processPacket(true); - else + pullStat->plcOverruns += mSkip; + } else processPacket(false); + pullStat->tick(); goto OUTPUT; } UNDERRUN : { processPacket(true); pullStat->plcUnderruns++; // count late + pullStat->tick(); goto OUTPUT; } @@ -334,17 +407,28 @@ ZERO_OUTPUT: OUTPUT: memcpy(buf, mXfrBuffer, mBytes); - pullStat->tick(); }; //******************************************************************************* void Regulator::processPacket(bool glitch) { + double tmp = 0.0; + if ((glitch) && (mFPPratioDenominator > 1)) { + glitch = !(mLastSeqNumOut % mFPPratioDenominator); + } + if (glitch) + tmp = (double)mIncomingTimer.nsecsElapsed(); for (int ch = 0; ch < mNumChannels; ch++) processChannel(ch, glitch, mPacketCnt, mLastWasGlitch); mLastWasGlitch = glitch; mPacketCnt++; // 32 bit is good for days: (/ (* (- (expt 2 32) 1) (/ 32 48000.0)) (* 60 60 24)) + + if (glitch) { + double tmp2 = (double)mIncomingTimer.nsecsElapsed() - tmp; + tmp2 /= 1000000.0; + pullStat->lastPLCdspElapsed = tmp2; + } } //******************************************************************************* @@ -367,9 +451,10 @@ void Regulator::processChannel(int ch, bool glitch, int packetCnt, bool lastWasG // LINEAR PREDICT DATA cd->mTail = cd->mTrain; - ba.predict(cd->mCoeffs, cd->mTail); // resizes to TRAINSAMPS-2 + TRAINSAMPS + ba.predict(cd->mCoeffs, + cd->mTail); // resizes to TRAINSAMPS-2 + TRAINSAMPS - for (int i = 0; i < (cd->trainSamps - 1); i++) + for (int i = 0; i < (cd->trainSamps - 2); i++) cd->mPrediction[i] = cd->mTail[i + cd->trainSamps]; } // cross fade last prediction with mTruth @@ -450,7 +535,7 @@ bool BurgAlgorithm::classify(double d) tmp = true; break; case FP_ZERO: - // qDebug() << ("zero"); + qDebug() << ("zero"); tmp = true; break; case FP_SUBNORMAL: @@ -470,8 +555,8 @@ void BurgAlgorithm::train(std::vector& coeffs, const std::vector Ak(m + 1, 0.0); @@ -575,8 +660,9 @@ ChanData::ChanData(int i, int FPP, int hist) : ch(i) } //******************************************************************************* -StdDev::StdDev(QElapsedTimer* timer, int w, int id) : mTimer(timer), window(w), mId(id) +StdDev::StdDev(int id, QElapsedTimer* timer, int w) : mId(id), mTimer(timer), window(w) { + window /= WindowDivisor; reset(); longTermStdDev = 0.0; longTermStdDevAcc = 0.0; @@ -587,21 +673,35 @@ StdDev::StdDev(QElapsedTimer* timer, int w, int id) : mTimer(timer), window(w), longTermMax = 0.0; longTermMaxAcc = 0.0; lastTime = 0.0; + lastPLCdspElapsed = 0.0; data.resize(w, 0.0); } void StdDev::reset() { - mean = 0.0; - // varRunning = 0.0; - acc = 0.0; - min = 999999.0; - max = 0.0; ctr = 0; + plcOverruns = 0; plcUnderruns = 0; + mean = 0.0; + acc = 0.0; + min = 999999.0; + max = -999999.0; +}; + +double StdDev::calcAuto(double autoHeadroom, double localFPPdur) +{ + // qDebug() << longTermStdDev << longTermMax << AutoMax << window << + // longTermCnt; + if ((longTermStdDev == 0.0) || (longTermMax == 0.0)) + return AutoMax; + double tmp = longTermStdDev + ((longTermMax > AutoMax) ? AutoMax : longTermMax); + if (tmp < localFPPdur) + tmp = localFPPdur; // might also check peerFPP... + tmp += autoHeadroom; + return tmp; }; -double StdDev::tick() +void StdDev::tick() { double now = (double)mTimer->nsecsElapsed() / 1000000.0; double msElapsed = now - lastTime; @@ -622,15 +722,15 @@ double StdDev::tick() var += (tmp * tmp); } var /= (double)window; - double stdDev = sqrt(var); + double stdDevTmp = sqrt(var); if (longTermCnt) { - longTermStdDevAcc += stdDev; + longTermStdDevAcc += stdDevTmp; longTermStdDev = longTermStdDevAcc / (double)longTermCnt; longTermMaxAcc += max; longTermMax = longTermMaxAcc / (double)longTermCnt; if (gVerboseFlag) cout << setw(10) << mean << setw(10) << lastMin << setw(10) << max - << setw(10) << stdDev << setw(10) << longTermStdDev << " " << mId + << setw(10) << stdDevTmp << setw(10) << longTermStdDev << " " << mId << endl; } else if (gVerboseFlag) cout << "printing directly from Regulator->stdDev->tick:\n (mean / min / " @@ -638,19 +738,21 @@ double StdDev::tick() "stdDev / longTermStdDev) \n"; longTermCnt++; - lastMean = mean; - lastMin = min; - lastMax = max; - lastStdDev = stdDev; + lastMean = mean; + lastMin = min; + lastMax = max; + lastPlcOverruns = plcOverruns; + lastPlcUnderruns = plcUnderruns; + lastStdDev = stdDevTmp; reset(); } - return lastTime; } + //******************************************************************************* bool Regulator::getStats(RingBuffer::IOStat* stat, bool reset) { QMutexLocker locker(&mMutex); - if (reset) { // all are unused + if (reset) { // all are unused, this is copied from superclass mUnderruns = 0; mOverflows = 0; mSkew0 = mLevel; @@ -661,68 +763,23 @@ bool Regulator::getStats(RingBuffer::IOStat* stat, bool reset) mBufIncCompensate = 0; mBroadcastSkew = 0; } + // hijack of struct IOStat { - stat->underruns = pullStat->lastPlcUnderruns; + stat->underruns = pullStat->lastPlcUnderruns + pullStat->lastPlcOverruns; #define FLOATFACTOR 1000.0 - stat->overflows = FLOATFACTOR * pushStat->longTermStdDev; - stat->skew = FLOATFACTOR * pushStat->lastMean; - stat->skew_raw = FLOATFACTOR * pushStat->lastMin; - stat->level = FLOATFACTOR * pushStat->lastMax; - stat->buf_dec_overflows = FLOATFACTOR * pushStat->lastStdDev; - + stat->overflows = FLOATFACTOR * pushStat->longTermStdDev; + stat->skew = FLOATFACTOR * pushStat->lastMean; + stat->skew_raw = FLOATFACTOR * pushStat->lastMin; + stat->level = FLOATFACTOR * pushStat->lastMax; + // stat->level = FLOATFACTOR * pushStat->longTermMax; + stat->buf_dec_overflows = FLOATFACTOR * pushStat->lastStdDev; + stat->autoq_corr = FLOATFACTOR * mMsecTolerance; stat->buf_dec_pktloss = FLOATFACTOR * pullStat->longTermStdDev; stat->buf_inc_underrun = FLOATFACTOR * pullStat->lastMean; stat->buf_inc_compensate = FLOATFACTOR * pullStat->lastMin; stat->broadcast_skew = FLOATFACTOR * pullStat->lastMax; stat->broadcast_delta = FLOATFACTOR * pullStat->lastStdDev; - // unused - // int32_t autoq_corr; - // int32_t autoq_rate; + stat->autoq_rate = FLOATFACTOR * pullStat->lastPLCdspElapsed; + // none are unused return true; } -/* -QString Regulator::getStats(uint32_t statCount, uint32_t lostCount) -{ - // formatting floats in columns looks better with std::stringstream than with - // QTextStream - QString tmp; - if (!statCount) { - tmp = QString("Regulator: inter-packet intervals msec\n"); - tmp += " (window of last "; - tmp += QString::number(pullStat->window); - tmp += " packets)\n"; - tmp += - "secs avgStdDev (mean min max stdDev) " - "PLC(under over skipped) lost\n"; - } else { - uint32_t lost = lostCount - mLastLostCount; - mLastLostCount = lostCount; -#define PDBL(x) << setw(10) << (QString("%1").arg(pushStat->x, 0, 'f', 2)).toStdString() -#define PDBL2(x) << setw(10) << (QString("%1").arg(pullStat->x, 0, 'f', 2)).toStdString() - std::stringstream logger; - logger << setw(2) - << statCount - PDBL(longTermStdDev) PDBL(lastMean) PDBL(lastMin) PDBL(lastMax) -PDBL(lastStdDev) - << setw(8) << pushStat->lastPlcSkipped - #ifndef GUIBS3 - // comment out this next line for GUI because... - << endl - // ...print all stats in one line when running in GUI because can't -handle extra crlf - // and... to actually see the two lines, need to run it in terminal - #endif - ; - tmp = QString::fromStdString(logger.str()); - std::stringstream logger2; - logger2 << setw(2) - << "" PDBL2(longTermStdDev) PDBL2(lastMean) PDBL2(lastMin) PDBL2(lastMax) - PDBL2(lastStdDev) - << setw(8) << pullStat->lastPlcUnderruns << setw(8) - << pullStat->lastPlcOverruns << setw(8) << pullStat->lastPlcSkipped - << setw(8) << lost << endl; - tmp += QString::fromStdString(logger2.str()); - } - return tmp; -} -*/ diff --git a/src/Regulator.h b/src/Regulator.h index 96d4e0e..9f66afc 100644 --- a/src/Regulator.h +++ b/src/Regulator.h @@ -35,8 +35,7 @@ * \date May 2021 */ -// EXPERIMENTAL for testing in JackTrip v1.4.0 -// Initial references and starter code +// Initial references and starter code to bring up Burg's recursion // http://www.emptyloop.com/technotes/A%20tutorial%20on%20Burg's%20method,%20algorithm%20and%20recursion.pdf // https://metacpan.org/source/SYP/Algorithm-Burg-0.001/README @@ -51,14 +50,6 @@ #include "AudioInterface.h" #include "RingBuffer.h" -//#define GUIBS3 -#ifdef GUIBS3 -#include - -#include "herlpergui.h" -#include "ui_herlpergui.h" -#endif - class BurgAlgorithm { public: @@ -95,46 +86,42 @@ class ChanData class StdDev { public: - StdDev(QElapsedTimer* timer, int w, int id); - void reset(); - double tick(); - QElapsedTimer* mTimer; - std::vector data; - double mean; - double var; - // double varRunning; - int window; + StdDev(int id, QElapsedTimer* timer, int w); + void tick(); + double calcAuto(double autoHeadroom, double localFPPdur); int mId; - double acc; - double min; - double max; - int ctr; + int plcOverruns; + int plcUnderruns; + double lastTime; double lastMean; double lastMin; double lastMax; - int plcUnderruns; + int lastPlcOverruns; int lastPlcUnderruns; + double lastPLCdspElapsed; double lastStdDev; double longTermStdDev; double longTermStdDevAcc; double longTermMax; double longTermMaxAcc; - double lastTime; + + private: + void reset(); + QElapsedTimer* mTimer; + std::vector data; + double mean; + int window; + double acc; + double min; + double max; + int ctr; int longTermCnt; }; -#ifdef GUIBS3 -class Regulator - : public QObject - , public RingBuffer -{ - Q_OBJECT; -#else class Regulator : public RingBuffer { -#endif public: - Regulator(int sample_rate, int channels, int bit_res, int FPP, int qLen); + Regulator(int rcvChannels, int bit_res, int FPP, int qLen, int bqLen); virtual ~Regulator(); void shimFPP(const int8_t* buf, int len, int seq_num); @@ -142,31 +129,39 @@ class Regulator : public RingBuffer // can hijack unused2 to propagate incoming seq num if needed // option is in UdpDataProtocol // if (!mJackTrip->writeAudioBuffer(src, host_buf_size, last_seq_num)) - // instread of + // instead of // if (!mJackTrip->writeAudioBuffer(src, host_buf_size, gap_size)) - virtual bool insertSlotNonBlocking(const int8_t* ptrToSlot, [[maybe_unused]] int len, - [[maybe_unused]] int seq_num) + virtual bool insertSlotNonBlockingRegulator(const int8_t* ptrToSlot, + [[maybe_unused]] int len, + [[maybe_unused]] int seq_num, int lostLen) { shimFPP(ptrToSlot, len, seq_num); + if (m_b_BroadcastQueueLength) + m_b_ReceiveRingBuffer->insertSlotNonBlocking(ptrToSlot, len, lostLen); return (true); } void pullPacket(int8_t* buf); virtual void readSlotNonBlocking(int8_t* ptrToReadSlot) { pullPacket(ptrToReadSlot); } + virtual void readBroadcastSlot(int8_t* ptrToReadSlot) + { + m_b_ReceiveRingBuffer->readSlotNonBlocking(ptrToReadSlot); + m_b_ReceiveRingBuffer->readBroadcastSlot(ptrToReadSlot); + } // virtual QString getStats(uint32_t statCount, uint32_t lostCount); virtual bool getStats(IOStat* stat, bool reset); private: - void setFPPratio(int len); + void setFPPratio(); bool mFPPratioIsSet; void processPacket(bool glitch); void processChannel(int ch, bool glitch, int packetCnt, bool lastWasGlitch); int mNumChannels; int mAudioBitRes; int mFPP; - int mSampleRate; + int mPeerFPP; uint32_t mLastLostCount; int mNumSlots; int mHist; @@ -191,7 +186,6 @@ class Regulator : public RingBuffer QElapsedTimer mIncomingTimer; int mLastSeqNumIn; int mLastSeqNumOut; - double mPacketDurMsec; std::vector mPhasor; std::vector mIncomingTiming; int mModSeqNum; @@ -199,16 +193,18 @@ class Regulator : public RingBuffer int mSkip; int mFPPratioNumerator; int mFPPratioDenominator; - int mPartialPacketCnt; + int mAssemblyCnt; + int mModCycle; bool mAuto; -#ifdef GUIBS3 - HerlperGUI* hg; - void updateGUI(double msTol, int nSlots, int lostWin); - public slots: -#endif + int mModSeqNumPeer; + double mAutoHeadroom; + double mFPPdurMsec; void changeGlobal(double); void changeGlobal_2(int); void changeGlobal_3(int); void printParams(); + /// Pointer for the Receive RingBuffer + RingBuffer* m_b_ReceiveRingBuffer; + int m_b_BroadcastQueueLength; }; #endif //__REGULATOR_H__ diff --git a/src/Reverb.cpp b/src/Reverb.cpp index 977fcbd..3f1774d 100644 --- a/src/Reverb.cpp +++ b/src/Reverb.cpp @@ -37,8 +37,6 @@ #include "Reverb.h" -#include - #include "jacktrip_types.h" //******************************************************************************* diff --git a/src/Reverb.h b/src/Reverb.h index 3464660..8970e08 100644 --- a/src/Reverb.h +++ b/src/Reverb.h @@ -41,6 +41,8 @@ #ifndef __REVERB_H__ #define __REVERB_H__ +#include + //#define SINE_TEST #include "ProcessPlugin.h" diff --git a/src/RingBuffer.cpp b/src/RingBuffer.cpp index 9e5ffc7..8e362bb 100644 --- a/src/RingBuffer.cpp +++ b/src/RingBuffer.cpp @@ -69,7 +69,7 @@ RingBuffer::RingBuffer(int SlotSize, int NumSlots) } // Advance write position to half of the RingBuffer - // Udpate Full Slots accordingly + // Update Full Slots accordingly mFullSlots = (NumSlots / 2); mLevelDownRate = 0.01; mStatUnit = 1; @@ -150,7 +150,7 @@ void RingBuffer::readSlotBlocking(int8_t* ptrToReadSlot) bool RingBuffer::insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen) { if (len != mSlotSize && 0 != len) { - // RingBuffer does not suppport mixed buf sizes + // RingBuffer does not support mixed buf sizes return false; } QMutexLocker locker(&mMutex); // lock the mutex @@ -168,7 +168,7 @@ bool RingBuffer::insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int los /// \todo It may be better here to insert the slot anyways, /// instead of not writing anything if (mFullSlots == mNumSlots) { - // std::cout << "OUPUT OVERFLOW NON BLOCKING = " << mNumSlots << std::endl; + // std::cout << "OUTPUT OVERFLOW NON BLOCKING = " << mNumSlots << std::endl; overflowReset(); return true; } @@ -183,6 +183,15 @@ bool RingBuffer::insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int los return true; } +//******************************************************************************* +bool RingBuffer::insertSlotNonBlockingRegulator([[maybe_unused]] const int8_t* ptrToSlot, + [[maybe_unused]] int len, + [[maybe_unused]] int seq_num, + [[maybe_unused]] int lostLen) +{ + return true; +} + //******************************************************************************* void RingBuffer::readSlotNonBlocking(int8_t* ptrToReadSlot) { diff --git a/src/RingBuffer.h b/src/RingBuffer.h index b517511..f7415cb 100644 --- a/src/RingBuffer.h +++ b/src/RingBuffer.h @@ -94,6 +94,12 @@ class RingBuffer */ virtual bool insertSlotNonBlocking(const int8_t* ptrToSlot, int len, int lostLen); + /** \brief Same as insertSlotNonBlocking but seq_num for Regulator + * \param ptrToSlot Pointer to slot to insert into the RingBuffer + */ + virtual bool insertSlotNonBlockingRegulator(const int8_t* ptrToSlot, int len, + int seq_num, int lostLen); + /** \brief Same as readSlotBlocking but non-blocking (asynchronous) * \param ptrToReadSlot Pointer to read slot from the RingBuffer */ diff --git a/src/RtAudioInterface.cpp b/src/RtAudioInterface.cpp index 1797d32..a40c1e1 100644 --- a/src/RtAudioInterface.cpp +++ b/src/RtAudioInterface.cpp @@ -155,7 +155,7 @@ void RtAudioInterface::setup() try { // IMPORTANT NOTE: It's VERY important to remember to pass this - // as the user data in the process callback, otherwise memeber won't + // as the user data in the process callback, otherwise member won't // be accessible mRtAudio->openStream(&out_params, &in_params, RTAUDIO_FLOAT32, sampleRate, &bufferFrames, &RtAudioInterface::wrapperRtAudioCallback, @@ -268,7 +268,7 @@ void RtAudioInterface::printDeviceInfo(unsigned int deviceId) cout << " --Default Output Device--" << endl; } if (info.isDefaultInput) { - cout << " --Default Intput Device--" << endl; + cout << " --Default Input Device--" << endl; } if (info.probed) { cout << " --Probed Successful--" << endl; diff --git a/src/RtAudioInterface.h b/src/RtAudioInterface.h index 5915ad4..51b6e9d 100644 --- a/src/RtAudioInterface.h +++ b/src/RtAudioInterface.h @@ -60,7 +60,7 @@ class RtAudioInterface : public AudioInterface /// \brief The class destructor virtual ~RtAudioInterface(); - /// \brief List all avialable audio interfaces, with its properties + /// \brief List all available audio interfaces, with its properties virtual void listAllInterfaces(); static void printDevices(); virtual int getDeviceIdFromName(std::string deviceName, bool isInput); diff --git a/src/Settings.cpp b/src/Settings.cpp index 4084c06..e964815 100644 --- a/src/Settings.cpp +++ b/src/Settings.cpp @@ -1015,7 +1015,7 @@ JackTrip* Settings::getConfiguredJackTrip() jackTrip->setSampleRate(mSampleRate); } - // Change defualt device ID + // Change default device ID if (mChangeDefaultID) { jackTrip->setDeviceID(mDeviceID); } diff --git a/src/UdpDataProtocol.cpp b/src/UdpDataProtocol.cpp index 6f61ad1..43d5df8 100644 --- a/src/UdpDataProtocol.cpp +++ b/src/UdpDataProtocol.cpp @@ -52,7 +52,7 @@ //#include #include //cc need SD_SEND #else -#include +#include #include // for POSIX Sockets #include #ifndef MANUAL_POLL @@ -217,7 +217,7 @@ int UdpDataProtocol::bindSocket() err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { - // Tell the user that we couldn't find a useable + // Tell the user that we couldn't find a usable // winsock.dll. return INVALID_SOCKET; @@ -226,7 +226,7 @@ int UdpDataProtocol::bindSocket() // Confirm that the Windows Sockets DLL supports 1.1. or higher if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { - // Tell the user that we couldn't find a useable + // Tell the user that we couldn't find a usable // winsock.dll. WSACleanup(); return INVALID_SOCKET; @@ -267,11 +267,36 @@ int UdpDataProtocol::bindSocket() #elif defined(__linux__) ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); #else - // This option is not avialable on Linux, and without it MAC OS X + // This option is not available on Linux, and without it MAC OS X // has problems rebinding a socket ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); #endif +#if defined(_WIN32) + // TODO: these don't seem to work on windows. we likely need to use qWAVE or qos2 +#elif defined(__APPLE__) + // set service type "Interactive Voice" + // TODO: this is supposed to be the right thing to do on OSX, but doesn't seem to do + // anything + const int val = NET_SERVICE_TYPE_VO; + ::setsockopt(sock_fd, SOL_SOCKET, SO_NET_SERVICE_TYPE, &val, sizeof(val)); +#else + // Set ToS to DSCP Expedited Forwarding (EF), recommended for Audio + // See RFC2474 https://datatracker.ietf.org/doc/html/rfc2474 + // See also + // https://www.slashroot.in/understanding-differentiated-services-tos-field-internet-protocol-header + const char tos = 0xB8; // 10111000 + if (mIPv6) { + ::setsockopt(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)); + } else { + ::setsockopt(sock_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + } + + // Set 802.1q QoS priority + int priority = 6; + ::setsockopt(sock_fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); +#endif + // Bind the Socket if (mIPv6) { if ((::bind(sock_fd, (struct sockaddr*)&local_addr6, sizeof(local_addr6))) < 0) { @@ -690,7 +715,7 @@ void UdpDataProtocol::waitForReady(int timeout_msec) int loop_resolution_usec = 100; // usecs to wait on each loop int emit_resolution_usec = 10000; // 10 milliseconds int timeout_usec = timeout_msec * 1000; - int elapsed_time_usec = 0; // Ellapsed time in milliseconds + int elapsed_time_usec = 0; // Elapsed time in milliseconds while (!datagramAvailable() && (elapsed_time_usec <= timeout_usec) && !mStopped) { // if (mStopped) { return false; } @@ -811,7 +836,7 @@ void UdpDataProtocol::receivePacketRedundancy( int ok = true; // send audio buf to ok = (mJackTrip->getBufferStrategy() !=3) ? // ring or jitter mJackTrip->writeAudioBuffer(src, host_buf_size, gap_size) - : mJackTrip->writeAudioBuffer(src, host_buf_size, last_seq_num); + : mJackTrip->writeAudioBufferRegulator(src, host_buf_size, last_seq_num, gap_size); if (!ok) { emit signalError("Local and Peer buffer settings are incompatible"); cout << "ERROR: Local and Peer buffer settings are incompatible" << endl; @@ -879,7 +904,7 @@ void UdpDataProtocol::sendPacketRedundancy(int8_t* full_redundant_packet, // Move older packets to end of array of redundant packets std::memmove(full_redundant_packet + full_packet_size, full_redundant_packet, full_packet_size * (mUdpRedundancyFactor - 1)); - // Copy new packet to the begining of array + // Copy new packet to the beginning of array std::memcpy(full_redundant_packet, mFullPacket, full_packet_size); // 10% (or other number) packet lost simulation. @@ -929,7 +954,7 @@ void UdpDataProtocol::sendPacketRedundancy(int8_t* full_redundant_packet, etc... Then, the receiving end checks if the firs packet in the list is the one it should use, - otherwise it continure reding the mUdpRedundancyFactor packets until it finds the one that + otherwise it continue reading the mUdpRedundancyFactor packets until it finds the one that should come next (this can better perfected by just jumping until the correct packet). If it has more than one packet that it hasn't yet received, it sends it to the soundcard one by one. diff --git a/src/UdpDataProtocol.h b/src/UdpDataProtocol.h index d437998..d3ffbe6 100644 --- a/src/UdpDataProtocol.h +++ b/src/UdpDataProtocol.h @@ -61,7 +61,7 @@ * The SENDER and RECEIVER socket can share the same port/address pair (for compatibility * with the JamLink boxes). This is achieved setting * the resusable property in the socket for address and port. You have to - * externaly check if the port is already binded if you want to avoid re-binding to the + * externally check if the port is already binded if you want to avoid re-binding to the * same port. */ class UdpDataProtocol : public DataProtocol @@ -98,9 +98,9 @@ class UdpDataProtocol : public DataProtocol /** \brief Receives a packet. It blocks until a packet is received * - * This function makes sure we recieve a complete packet + * This function makes sure we receive a complete packet * of size n - * \param buf Buffer to store the recieved packet + * \param buf Buffer to store the received packet * \param n size of packet to receive * \return number of bytes read, -1 on error */ @@ -141,7 +141,8 @@ class UdpDataProtocol : public DataProtocol /** \brief Implements the Thread Loop. To start the thread, call start() * ( DO NOT CALL run() ) * - * This function creats and binds all the socket and start the connection loop thread. + * This function creates and binds all the socket and start the connection loop + * thread. */ virtual void run(); @@ -180,14 +181,14 @@ class UdpDataProtocol : public DataProtocol */ void waitForReady(int timeout_msec); - /** \brief Redundancy algorythm at the receiving end + /** \brief Redundancy algorithm at the receiving end */ virtual void receivePacketRedundancy(int8_t* full_redundant_packet, int full_redundant_packet_size, int full_packet_size, uint16_t& current_seq_num, uint16_t& last_seq_num, uint16_t& newer_seq_num); - /** \brief Redundancy algorythm at the sender's end + /** \brief Redundancy algorithm at the sender's end */ virtual void sendPacketRedundancy(int8_t* full_redundant_packet, int full_redundant_packet_size, diff --git a/src/UdpHubListener.cpp b/src/UdpHubListener.cpp index 56ab0cc..5ee1806 100644 --- a/src/UdpHubListener.cpp +++ b/src/UdpHubListener.cpp @@ -130,7 +130,7 @@ UdpHubListener::~UdpHubListener() } //******************************************************************************* -// Now that the first handshake is with TCP server, if the addreess/peer port of +// Now that the first handshake is with TCP server, if the address/peer port of // the client is already on the thread pool, it means that a new connection is // requested (the old was disconnected). So we have to remove that thread from // the pool and then connect again. @@ -153,7 +153,7 @@ void UdpHubListener::start() if (mRequireAuth) { cout << "JackTrip HUB SERVER: Enabling authentication" << endl; - // Check that SSL is avaialable + // Check that SSL is available bool error = false; QString error_message; if (!QSslSocket::supportsSsl()) { @@ -316,7 +316,7 @@ void UdpHubListener::receivedClientInfo(QSslSocket* clientConnection) // Create a new JackTripWorker, but don't check if this is coming from an existing ip // or port yet. We need to wait until we receive the port value from the UDP header to - // accomodate NAT. + // accommodate NAT. // ----------------------------- int id = getJackTripWorker(PeerAddress.toString(), peer_udp_port, clientName); diff --git a/src/build b/src/build index 5d36142..4e38032 100755 --- a/src/build +++ b/src/build @@ -4,7 +4,7 @@ if [[ -t 1 ]]; then echo -e "\033[1;31mIMPORTANT\033[0m" echo "The build script is now located in the root jacktrip folder and should" echo "be run from there in future. (This script will eventually be removed and" - echo "is included for compatability during the transition.)" + echo "is included for compatibility during the transition.)" echo echo "In future, after cloning the repository with git use the following commands:" echo -e "\033[1m$ cd jacktrip" diff --git a/src/dblsqd/feed.cpp b/src/dblsqd/feed.cpp new file mode 100644 index 0000000..a24215e --- /dev/null +++ b/src/dblsqd/feed.cpp @@ -0,0 +1,332 @@ +#include "feed.h" + +namespace dblsqd +{ + +/*! + \class Feed + * \brief The Feed class provides methods for accessing DBLSQD Feeds and downloading + Releases. + * + * A Feed is a representation of an Application’s Releases. + * This class can retrieve Feeds via HTTP(S) and offers convenience methods for + * + * \section3 Loading Feeds + * + * Before a Feed can be loaded with load(), it needs to be initialized with setUrl(). + * + * \section3 Downloading Updates + * This class also allows downloading updates through the downloadRelease() method. + */ + +/*! + * \brief Constructs a new Feed object. + * + * \sa setUrl() + */ +Feed::Feed(QString baseUrl, QString channel, QString os, QString arch, QString type) + : feedReply(NULL) + , downloadReply(NULL) + , downloadFile(NULL) + , redirects(0) + , _ready(false) +{ + if (!baseUrl.isEmpty()) { + this->setUrl(baseUrl, channel, os, arch, type); + } +} + +/*! + * \brief Sets the Feed URL. + * + * This method can be used to manually set the Feed URL. + */ +void Feed::setUrl(QUrl url) +{ + this->url = url; +} + +/*! + * \brief Sets the Feed URL by specifying its components. + * + * The only required component is baseUrl which must be the base URL for an Application + * provided by the DBSLQD CLI Tool. It should include the full schema and does not require + * a trailing "/". + */ +void Feed::setUrl(QString baseUrl, QString channel, QString os, QString arch, + QString type) +{ + QStringList urlParts; + urlParts << baseUrl; + urlParts << channel; + + if (!os.isEmpty()) { + urlParts << os; + } else { + QString autoOs = QSysInfo::productType().toLower(); + if (autoOs == "windows") { + autoOs = "win"; + } else if (autoOs == "osx" || autoOs == "macos") { + autoOs = "mac"; + } else { + autoOs = QSysInfo::kernelType(); + } + urlParts << autoOs; + } + + if (!arch.isEmpty()) { + urlParts << arch; + } else { + QString autoArch = QSysInfo::buildCpuArchitecture(); + if (autoArch == "i386" || autoArch == "i586" || autoArch == "i586") { + autoArch = "x86"; + } + urlParts << autoArch; + } + + if (!type.isEmpty()) { + urlParts << "?t=" + type; + } + + this->url = QUrl(urlParts.join("/")); +} + +/*! + * \brief Returns the Feed URL. + */ +QUrl Feed::getUrl() +{ + return QUrl(url); +} + +/*! + * \brief Returns a list of all Releases in the Feed. + * + * The list is sorted in descending order by version number/release date. + * If called before ready() was emitted, an empty list is returned. + * \sa getReleases() + */ +QList Feed::getReleases() +{ + return releases; +} + +/*! + * \brief Returns a list of all Releases in the Feed that are newer than the given + * Release. + * + * The list is sorted in descending order by version number/release date. + * If called before ready() was emitted, an empty list is returned. + * \sa getReleases() + */ +QList Feed::getUpdates(Release currentRelease) +{ + QList updates; + for (int i = 0; i < releases.size(); i++) { + if (currentRelease < releases.at(i)) + updates << releases.at(i); + } + return updates; +} + +/*! + * \brief Returns the pointer to a QTemporaryFile for a downloaded file. + * + * If called before downloadFinished() was emitted, this might return a NULL + * pointer. + */ +QTemporaryFile* Feed::getDownloadFile() +{ + return downloadFile; +} + +/*! + * \brief Returns true if Feed information has been retrieved successfully. + * + * A ready Feed might not contain any release information. + * If downloading the Feed failed, false is returned. + */ +bool Feed::isReady() +{ + return _ready; +} + +/* + * Async API functions + */ +/*! + * \brief Retrieves and parses data from the Feed. + * + * A Feed URL must have been set before with setUrl(). Emits ready() or loadError() on + * completion. + */ +void Feed::load() +{ + if (feedReply != NULL && !feedReply->isFinished()) { + return; + } + + QNetworkRequest request(getUrl()); + feedReply = nam.get(request); + connect(feedReply, SIGNAL(finished()), this, SLOT(handleFeedFinished())); +} + +/*! + * \brief Starts the download of a given Release. + * \sa downloadFinished() downloadError() downloadProgress() + */ +void Feed::downloadRelease(Release release) +{ + redirects = 0; + makeDownloadRequest(release.getDownloadUrl()); + this->release = release; +} + +/* + * Private methods + */ +void Feed::makeDownloadRequest(QUrl url) +{ + if (downloadReply != NULL && !downloadReply->isFinished()) { + disconnect(downloadReply); + downloadReply->abort(); + downloadReply->deleteLater(); + } + if (downloadFile != NULL) { + disconnect(downloadFile); + downloadFile->close(); + downloadFile->deleteLater(); + downloadFile = NULL; + } + + QNetworkRequest request(url); + downloadReply = nam.get(request); + connect(downloadReply, SIGNAL(downloadProgress(qint64, qint64)), this, + SLOT(handleDownloadProgress(qint64, qint64))); + connect(downloadReply, SIGNAL(readyRead()), this, SLOT(handleDownloadReadyRead())); + connect(downloadReply, SIGNAL(finished()), this, SLOT(handleDownloadFinished())); +} + +/* + * Signals + */ +/*! \fn void Feed::ready() + * This signal is emitted when a Feed has been successfully downloaded and parsed. + * \sa loadError() load() + */ + +/*! \fn void Feed::loadError(QString message) + * This signal is emitted when a Feed could not be downloaded. + * When loadError() is emitted, ready() is not emitted. + * \sa ready() load() + */ + +/*! \fn void Feed::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) + * This signal is emitted during the download of a Release through downloadRelease(). + * \sa downloadRelease() + */ + +/*! \fn void Feed::downloadFinished() + * This signal is emitted when the download of a Release was successful. + * A QTemporaryFile* of the downloaded file can then be retrieved with getDownloadFile(). + * \sa downloadRelease() + */ + +/*! \fn void Feed::downloadError() + * This signal is emitted when there was an error downloading or verifying a Release. + * When downloadError() is emitted, downloadFinished() is not emitted. + * \sa downloadFinished() downloadRelease() + */ + +/* + * Private Slots + */ +void Feed::handleFeedFinished() +{ + if (feedReply->error() != QNetworkReply::NoError) { + emit loadError(feedReply->errorString()); + return; + } + + releases.clear(); + QByteArray json = feedReply->readAll(); + QJsonDocument doc = QJsonDocument::fromJson(json); + QJsonArray releasesInfo = doc.object().value("releases").toArray(); + for (int i = 0; i < releasesInfo.size(); i++) { + releases << Release(releasesInfo.at(i).toObject()); + } + std::sort(releases.begin(), releases.end()); + std::reverse(releases.begin(), releases.end()); + + _ready = true; + emit ready(); +} + +void Feed::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + emit downloadProgress(bytesReceived, bytesTotal); +} + +void Feed::handleDownloadReadyRead() +{ + if (downloadFile == NULL) { + QString fileName = downloadReply->url().fileName(); + // Workaround for dblsqd to extract filename via query params from a + // Github-formatted redirect URL + QUrl url = downloadReply->url(); + QString host = url.host(); + if (host.contains("github", Qt::CaseInsensitive) && url.hasQuery()) { + QString query = url.query(); + QRegExp rx("filename%3D(.*)(&|$)"); + rx.setMinimal(true); + if (rx.indexIn(query) > -1) { + fileName = rx.cap(1); + } + } + // End workaround + int extensionPos = fileName.indexOf(QRegExp("(?:\\.tar)?\\.[a-zA-Z0-9]+$")); + if (extensionPos > -1) { + fileName.insert(extensionPos, "-XXXXXX"); + } + downloadFile = new QTemporaryFile(QDir::tempPath() + "/" + fileName); + downloadFile->open(); + } + downloadFile->write(downloadReply->readAll()); +} + +void Feed::handleDownloadFinished() +{ + if (downloadReply->error() != QNetworkReply::NoError) { + emit downloadError(downloadReply->errorString()); + return; + } else if (!downloadReply->attribute(QNetworkRequest::RedirectionTargetAttribute) + .isNull()) { + if (redirects >= 8) { + emit downloadError(tr("Too many redirects.")); + return; + } + QUrl redirectionTarget = + downloadReply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); + QUrl redirectedUrl = downloadReply->url().resolved(redirectionTarget); + redirects++; + makeDownloadRequest(redirectedUrl); + return; + } else if (downloadFile == NULL) { + emit downloadError(tr("No data received from server")); + return; + } + + downloadFile->flush(); + downloadFile->seek(0); + QCryptographicHash fileHash(QCryptographicHash::Sha256); + fileHash.addData(downloadFile->readAll()); + QString hashResult = fileHash.result().toHex(); + if (hashResult.toLower() != release.getDownloadSHA256().toLower()) { + emit downloadError(tr("Could not verify download integrity.")); + return; + } + + emit downloadFinished(); +} + +} // namespace dblsqd diff --git a/src/dblsqd/feed.h b/src/dblsqd/feed.h new file mode 100644 index 0000000..fc030ac --- /dev/null +++ b/src/dblsqd/feed.h @@ -0,0 +1,67 @@ +#ifndef DBLSQD_FEED_H +#define DBLSQD_FEED_H + +#include +#include + +#include "release.h" + +namespace dblsqd +{ + +class Feed : public QObject +{ + Q_OBJECT + + public: + Feed(QString baseUrl = "", QString channel = "release", QString os = QString(), + QString arch = QString(), QString type = QString()); + + void setUrl(QUrl url); + void setUrl(QString baseUrl, QString channel = "release", QString os = QString(), + QString arch = QString(), QString type = QString()); + QUrl getUrl(); + + // Async API + void load(); + void downloadRelease(Release release); + + // Sync API + QList getUpdates( + Release currentRelease = Release(QCoreApplication::applicationVersion())); + QList getReleases(); + QTemporaryFile* getDownloadFile(); + bool isReady(); + + signals: + void ready(); + void loadError(QString message); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadFinished(); + void downloadError(QString message); + + private: + QUrl url; + + QList releases; + + void makeDownloadRequest(QUrl url); + + QNetworkAccessManager nam; + QNetworkReply* feedReply; + Release release; + QNetworkReply* downloadReply; + QTemporaryFile* downloadFile; + uint redirects; + bool _ready; + + private slots: + void handleFeedFinished(); + void handleDownloadProgress(qint64, qint64); + void handleDownloadReadyRead(); + void handleDownloadFinished(); +}; + +} // namespace dblsqd + +#endif // DBLSQD_FEED_H diff --git a/src/dblsqd/release.cpp b/src/dblsqd/release.cpp new file mode 100644 index 0000000..e1c728a --- /dev/null +++ b/src/dblsqd/release.cpp @@ -0,0 +1,143 @@ +#include "release.h" + +namespace dblsqd +{ + +/*! + * \class Release + * \brief This class is used to represent information about a single Release + * from a Feed. + */ + +/*! + * \brief Constructs a new Release from q QJsonObject. + */ +Release::Release(QJsonObject releaseInfo) +{ + this->version = releaseInfo.value("version").toString(); + this->changelog = releaseInfo.value("changelog").toString(); + + QJsonObject downloadInfo = releaseInfo.value("download").toObject(); + this->date = + QDateTime::fromString(downloadInfo.value("date").toString(), Qt::ISODate); + this->downloadUrl = QUrl(downloadInfo.value("url").toString()); + this->downloadSize = downloadInfo.value("size").toDouble(); + this->downloadSHA1 = downloadInfo.value("sha1").toString(); + this->downloadSHA256 = downloadInfo.value("sha256").toString(); + this->downloadDSA = downloadInfo.value("dsa").toString(); +} + +/*! + * \brief Constructs a new Release from a version string and a date. + * + * This method is useful when constructing a "virtual" Release for comparing + * it with Releases retrieved from a Feed. + */ +Release::Release(QString version, QDateTime date) + : version(version) + , date(date) + , changelog("") + , downloadUrl("") + , downloadSize(0) + , downloadSHA1("") + , downloadSHA256("") + , downloadDSA("") +{ +} + +/*! + * \brief Compares two Releases. + * + * If the Release version is compatible with SemVer, the version determines + * Release order. If versions are identical or not compatible with SemVer, + * Release date is used for determining order instead. + */ +bool operator<(const Release& one, const Release& other) +{ + SemVer v1(one.version); + SemVer v2(other.version); + if (v1.isValid() && v2.isValid()) { + return (v1 < v2); + } else { + return (one.date < other.date); + } +} + +bool operator==(const Release& one, const Release& other) +{ + return one.version == other.version; +} + +bool operator<=(const Release& one, const Release& other) +{ + return one == other || one < other; +} + +/* + * Getters + */ +/*! + * \brief Returns the Release version. + */ +QString Release::getVersion() const +{ + return this->version; +} + +/*! + * \brief Returns the Release changelog. + */ +QString Release::getChangelog() const +{ + return this->changelog; +} + +/*! + * \brief Returns the Release date. + */ +QDateTime Release::getDate() const +{ + return this->date; +} + +/*! + * \brief Returns the Release download URL. + */ +QUrl Release::getDownloadUrl() const +{ + return this->downloadUrl; +} + +/*! + * \brief Returns the SHA1 hash of the Release download. + */ +QString Release::getDownloadSHA1() const +{ + return this->downloadSHA1; +} + +/*! + * \brief Returns the SHA256 hash of the Release download. + */ +QString Release::getDownloadSHA256() const +{ + return this->downloadSHA256; +} + +/*! + * \brief Returns the DSA signature of the Release download. + */ +QString Release::getDownloadDSA() const +{ + return this->downloadDSA; +} + +/*! + * \brief Returns the size of the Release download in bytes. + */ +qint64 Release::getDownloadSize() const +{ + return (qint64)this->downloadSize; +} + +} // namespace dblsqd diff --git a/src/dblsqd/release.h b/src/dblsqd/release.h new file mode 100644 index 0000000..9c769bf --- /dev/null +++ b/src/dblsqd/release.h @@ -0,0 +1,44 @@ +#ifndef DBLSQD_RELEASE_H +#define DBLSQD_RELEASE_H + +#include +#include + +#include "semver.h" + +namespace dblsqd +{ + +class Release +{ + public: + Release(QJsonObject releaseInfo); + Release(QString version = QString(), QDateTime date = QDateTime()); + + friend bool operator<(const Release& one, const Release& other); + friend bool operator==(const Release& one, const Release& other); + friend bool operator<=(const Release& one, const Release& other); + + QString getVersion() const; + QString getChangelog() const; + QDateTime getDate() const; + QUrl getDownloadUrl() const; + QString getDownloadSHA1() const; + QString getDownloadSHA256() const; + QString getDownloadDSA() const; + qint64 getDownloadSize() const; + + private: + QString version; + QDateTime date; + QString changelog; + QUrl downloadUrl; + long downloadSize; + QString downloadSHA1; + QString downloadSHA256; + QString downloadDSA; +}; + +} // namespace dblsqd + +#endif // DBLSQD_RELEASE_H diff --git a/src/dblsqd/semver.cpp b/src/dblsqd/semver.cpp new file mode 100644 index 0000000..0aa77d0 --- /dev/null +++ b/src/dblsqd/semver.cpp @@ -0,0 +1,87 @@ +#include "semver.h" + +namespace dblsqd +{ + +/*! + * \class SemVer + * \brief SemVer encapsulates a version according to + * Semantic Versioning 2.0. + */ + +/*! + * \brief Constructs a new SemVer object from a string. + */ +SemVer::SemVer(QString version) : original(version), valid(false) +{ + QRegExp rx(getRegExp()); + if (rx.indexIn(version) > -1) { + this->major = rx.cap(1).toInt(); + this->minor = rx.cap(2).toInt(); + this->patch = rx.cap(3).toInt(); + this->prerelease = rx.cap(4); + this->build = rx.cap(5); + this->valid = true; + } else { + this->major = 0; + this->minor = 0; + this->patch = 0; + this->prerelease = ""; + this->build = ""; + this->valid = false; + } +} + +/*! + * \brief Returns true if this version is valid according to the SemVer + * specification. Otherwise returns false. + */ +bool SemVer::isValid() const +{ + return this->valid; +} + +/*! + * \brief Compares two SemVer objects. + * + * Returns true if the left-hand SemVer object represents a lower version + * according to the SemVer 2.0 specification. + * Otherweise returns false. + * Returns false if one of the SemVer objects does not represent a valid + * SemVer. + * \sa isValid() + */ +bool SemVer::operator<(const SemVer& other) +{ + if (!this->isValid() || !other.isValid()) { + return false; + } + + if (this->major != other.major) { + return this->major < other.major; + } else if (this->minor != other.minor) { + return this->minor < other.minor; + } else if (this->patch != other.patch) { + return this->patch < other.patch; + } else if (this->prerelease != other.prerelease) { + if (this->prerelease == "") { + return false; + } else if (other.prerelease == "") { + return true; + } + return (QString::localeAwareCompare(this->prerelease, other.prerelease) < 0); + } else { + return (QString::localeAwareCompare(this->build, other.build) < 0); + } +} + +QString SemVer::getRegExp() +{ + QString v = "(0|[1-9]\\d*)"; + QString p = + "(?:-((?:0|[1-9A-Za-z][0-9A-Za-z]*)(?:\\.(?:0|[1-9A-Za-z][0-9A-Za-z]*))*))?"; + QString b = "(?:\\+((?:[0-9A-Za-z]*)(?:\\.(?:[0-9A-Za-z][0-9A-Za-z]*))*))?"; + return "^" + v + "." + v + "." + v + p + b + "$"; +} + +} // namespace dblsqd diff --git a/src/dblsqd/semver.h b/src/dblsqd/semver.h new file mode 100644 index 0000000..18ebcda --- /dev/null +++ b/src/dblsqd/semver.h @@ -0,0 +1,34 @@ +#ifndef DBLSQD_SEMVER_H +#define DBLSQD_SEMVER_H + +#include +#include + +namespace dblsqd +{ + +class SemVer +{ + public: + SemVer(QString version); + + bool operator<(const SemVer& other); + + bool isValid() const; + QString toString(); + + private: + QString original; + int major; + int minor; + int patch; + QString prerelease; + QString build; + bool valid; + + static QString getRegExp(); +}; + +} // namespace dblsqd + +#endif // DBLSQD_SEMVER_H diff --git a/src/dblsqd/update_dialog.cpp b/src/dblsqd/update_dialog.cpp new file mode 100644 index 0000000..bb914ef --- /dev/null +++ b/src/dblsqd/update_dialog.cpp @@ -0,0 +1,694 @@ +#include "update_dialog.h" + +#include "ui_update_dialog.h" + +namespace dblsqd +{ + +/*! + * \class UpdateDialog + * \brief A dialog class for displaying and downloading update information. + * + * UpdateDialog is a drop-in class for adding a fully-functional auto-update + * component to an existing application. + * + * The most simple integration is + * possible with just three lines of code: + * \code + * dblsqd::Feed* feed = new dblsqd::Feed(); + * feed->setUrl("https://feeds.dblsqd.com/:app_token"); + * dblsqd::UpdateDialog* updateDialog = new dblsqd::UpdateDialog(feed); + * \endcode + * + * The update dialog can also display an application icon which can be set with + * setIcon(). + */ + +/*! + * \enum UpdateDialog::Type + * \brief This flag determines the if and when the UpdateDialog is displayed + * automatically. + * + * *OnUpdateAvailable*: Automatically display the dialog as soon as the Feed + * has been downloaded and parsed and if there is a newer version than the + * current version returned by QCoreApplication::applicationVersion(). + * + * *OnLastWindowClosed*: If there is a newer version available than the current + * version returned by QCoreApplication::applicationVersion(), the update + * dialog is displayed when QGuiApplication emits the lastWindowClosed() event. + * Note that when this flag is used, + * QGuiApplication::setQuitOnLastWindowClosed(false) will be called. + * + * *Manual*: The dialog is only displayed when explicitly requested via show() + * or exec(). + * Note that update information might not be available instantly after + * constructing an UpdateDialog. + * + * *ManualChangelog*: The dialog is only displayed when explicitly requested via + * show() or exec(). + * Instead of the full update interface, only the changelog will be shown. + */ + +/*! + * \brief Constructs a new UpdateDialog. + * + * A Feed object needs to be constructed first and passed to this constructor. + * Feed::load() does not need to be called on the Feed object. + * + * The given UpdateDialog::Type flag determines when/if the dialog is shown + * automatically. + * + * UpdateDialog uses QSettings to save information such as when a release was + * skipped by the users. If you want to use a specially initialized QSettings + * object, you may also pass it to this constructor. + * + */ +UpdateDialog::UpdateDialog(Feed* feed, int type, QWidget* parent, QSettings* settings) + : QDialog(parent) + , ui(new Ui::UpdateDialog) + , feed(feed) + , type(type) + , settings(settings) + , accepted(false) + , isDownloadFinished(false) + , acceptedInstallButton(NULL) +{ + ui->setupUi(this); + + QPalette palette = this->palette(); + QString textColor = palette.color(QPalette::Text).name(); + QString backgroundColor = palette.color(QPalette::Base).name(); + QString labelChangelogStyle = + QString("color: %1; background: %2").arg(textColor, backgroundColor); + ui->labelChangelog->setStyleSheet(labelChangelogStyle); + + ui->buttonCancel->addAction(ui->actionCancel); + ui->buttonCancel->addAction(ui->actionSkip); + ui->buttonCancel->setDefaultAction(ui->actionCancel); + + _openExternalLinks = true; + connect(ui->labelChangelog, SIGNAL(linkActivated(QString)), this, + SLOT(onLinkActivated(QString))); + + switch (type) { + case OnUpdateAvailable: { + connect(this, SIGNAL(ready()), this, SLOT(showIfUpdatesAvailable())); + break; + } + case OnLastWindowClosed: { + QGuiApplication* app = (QGuiApplication*)QApplication::instance(); + app->setQuitOnLastWindowClosed(false); + connect(app, SIGNAL(lastWindowClosed()), this, + SLOT(showIfUpdatesAvailableOrQuit())); + break; + } + case Manual: { + // don’t do anything + } + } + + if (feed->isReady()) { + handleFeedReady(); + } else { + setupLoadingUi(); + feed->load(); + connect(feed, SIGNAL(ready()), this, SLOT(handleFeedReady())); + } +} + +UpdateDialog::~UpdateDialog() +{ + delete ui; +} + +/* + * Setters + */ +/*! + * \brief Sets the icon displayed in the update window. + */ +void UpdateDialog::setIcon(QPixmap pixmap) +{ + ui->labelIcon->setPixmap(QPixmap(pixmap)); + ui->labelIcon->setHidden(false); +} + +void UpdateDialog::setIcon(QString fileName) +{ + ui->labelIcon->setPixmap(QPixmap(fileName)); + ui->labelIcon->setHidden(false); +} + +/*! + * \brief Sets the minimum version to be displayed in the changelog. + * Defaults to QApplication::applicationVersion() if not set. + * \param version + */ +void UpdateDialog::setMinVersion(QString version) +{ + _minVersion = version; + setupChangelogUi(); +} + +/*! + * \brief Sets the maximum version to be displayed in the changelog + * \param version + */ +void UpdateDialog::setMaxVersion(QString version) +{ + _maxVersion = version; + setupChangelogUi(); +} + +/*! + * \brief Convenience method for setting minimum and maximum version to be displayed in + * the changelog. maximumVersion is set to QApplication::applicationVersion() + * + * \param previousVersion + */ +void UpdateDialog::setPreviousVersion(QString previousVersion) +{ + _previousVersion = previousVersion; + _minVersion = previousVersion; + _maxVersion = QApplication::applicationVersion(); + setupChangelogUi(); +} + +/*! + * \brief Adds a custom button for handling update installation. + * \param button + * + * When the custom button is clicked after an update has been downloaded or when + * downloading an update that was started by clicking the button has finished, + * installButtonClicked(QAbstractButton* button, QString filePath) is emitted. + */ +void UpdateDialog::addInstallButton(QAbstractButton* button) +{ + installButtons.append(button); + ui->buttonContainer->layout()->addWidget(button); + if (isVisible() && ui->buttonCancel->isVisible()) { + setupUpdateUi(); + } +} + +/*! + * \propget UpdateExternalLinks + * + * Determines if links in the changelog should be opened automatically by + * QDesktopServices::openUrl() when a user clicks on them. + * If set to false, the linkActivated() signal is emitted instead. + * + * The default value is true. + + */ +bool UpdateDialog::openExternalLinks() +{ + return _openExternalLinks; +} + +/*! + * \propset UpdateDialog::openExternalLinks + */ +void UpdateDialog::setOpenExternalLinks(bool open) +{ + _openExternalLinks = open; +} + +/* + * Public Slots + */ +/*! + * \brief Default handler for the install button. + * + * Closes the dialog if no other action (such as + * downloading or installing a Release) is required first. + */ +void UpdateDialog::onButtonInstall() +{ + accepted = true; + if (isDownloadFinished) { + startUpdate(); + } else if (!latestRelease.getVersion().isEmpty()) { + startDownload(); + } else { + done(QDialog::Accepted); + } +} + +void UpdateDialog::onButtonCustomInstall() +{ + accepted = true; + if (isDownloadFinished) { + emit installButtonClicked((QAbstractButton*)sender(), updateFilePath); + } else if (!latestRelease.getVersion().isEmpty()) { + acceptedInstallButton = (QAbstractButton*)sender(); + startDownload(); + } else { + done(QDialog::Accepted); + } +} + +/*! + * \brief Skips the latest retrieved Release. + * + * If a release has been skipped, UpdateDialog will not be displayed + * automatically when using Type::OnUpdateAvailable or + * Type::OnLastWindowClosed. + */ +void UpdateDialog::skip() +{ + if (!updateFilePath.isEmpty()) { + QFile::remove(updateFilePath); + } + setSettingsValue("skipRelease", latestRelease.getVersion(), settings); + done(QDialog::Rejected); +} + +/*! + * \brief Shows the dialog if there are available updates. + */ +void UpdateDialog::showIfUpdatesAvailable() +{ + QString latestVersion = latestRelease.getVersion(); + bool skipRelease = + (settingsValue("skipRelease", "", settings).toString() == latestVersion); + if (!latestVersion.isEmpty() && !skipRelease) { + show(); + } +} + +/*! + * \brief Shows the dialog if there are updates available or quits the application. + */ +void UpdateDialog::showIfUpdatesAvailableOrQuit() +{ + if (type == OnLastWindowClosed) { + QGuiApplication* app = (QGuiApplication*)QApplication::instance(); + app->setQuitOnLastWindowClosed(true); + disconnect(app, SIGNAL(lastWindowClosed()), this, + SLOT(showIfUpdatesAvailableOrQuit())); + } + QString latestVersion = latestRelease.getVersion(); + bool skipRelease = + (settingsValue("skipRelease", "", settings).toString() == latestVersion); + if (!latestVersion.isEmpty() && !skipRelease) { + show(); + } else { + QCoreApplication::quit(); + } +} + +/* + * Static settings helpers + */ +QVariant UpdateDialog::settingsValue(QString key, QVariant defaultValue, + QSettings* settings) +{ + return settings->value("DBLSQD/" + key, defaultValue); +} + +void UpdateDialog::setSettingsValue(QString key, QVariant value, QSettings* settings) +{ + settings->setValue("DBLSQD/" + key, value); +} + +void UpdateDialog::removeSetting(QString key, QSettings* settings) +{ + settings->remove("DBLSQD/" + key); +} + +void UpdateDialog::setDefaultSettingsValue(QString key, QVariant value, + QSettings* settings) +{ + if (settings->contains("DBLSQD/" + key)) + return; + setSettingsValue(key, value, settings); +} + +/*! + * \brief Enables or disables automatic downloads. + */ +void UpdateDialog::enableAutoDownload(bool enabled, QSettings* settings) +{ + setSettingsValue("autoDownload", enabled, settings); +} + +/*! + * \brief Returns true if automatic downloads are enabled. + * + * If defaultValue is provided, it is stored if no other value has previously been set. + */ +bool UpdateDialog::autoDownloadEnabled(QVariant defaultValue, QSettings* settings) +{ + if (defaultValue.isValid()) { + setDefaultSettingsValue("autoDownload", defaultValue, settings); + } else { + defaultValue = false; + } + return settingsValue("autoDownload", defaultValue, settings).toBool(); +} + +/*! + * \overload + */ +bool UpdateDialog::autoDownloadEnabled(QSettings* settings) +{ + return settingsValue("autoDownload", false, settings).toBool(); +} + +/* + * Helpers + */ + +void UpdateDialog::adjustDialogSize() +{ + adjustSize(); + +/*HACK: Qt seems to incorrectly calculate window geometry on Windows. + This code avoids warning messages logged by the application + in that case.*/ +#if defined(Q_OS_WIN) || defined(Q_WS_WIN) + QSize dialogSize = size(); + resize(dialogSize.width(), dialogSize.height() + 3); +#endif +} + +void UpdateDialog::resetUi() +{ + QList hiddenWidgets; + for (int i = 0; i < installButtons.size(); i++) { + hiddenWidgets << installButtons.at(i); + } + hiddenWidgets << ui->headerContainer << ui->labelIcon << ui->headerContainerLoading + << ui->headerContainerNoUpdates << ui->headerContainerChangelog + << ui->scrollAreaChangelog << ui->progressBar << ui->checkAutoDownload + << ui->buttonCancel << ui->buttonCancelLoading << ui->buttonConfirm + << ui->buttonInstall; + for (int i = 0; i < hiddenWidgets.size(); i++) { + hiddenWidgets.at(i)->hide(); + hiddenWidgets.at(i)->disconnect(); + } + ui->progressBar->reset(); + adjustDialogSize(); +} + +void UpdateDialog::setupLoadingUi() +{ + resetUi(); + ui->headerContainerLoading->show(); + ui->progressBar->show(); + ui->progressBar->setMaximum(0); + ui->progressBar->setMinimum(0); + ui->buttonCancelLoading->show(); + ui->buttonCancelLoading->setFocus(); + connect(ui->buttonCancelLoading, SIGNAL(clicked(bool)), this, SLOT(reject())); + adjustDialogSize(); +} + +void UpdateDialog::setupUpdateUi() +{ + resetUi(); + + QList showWidgets; + showWidgets << ui->headerContainer << ui->scrollAreaChangelog << ui->checkAutoDownload + << ui->buttonCancel << ui->buttonInstall; + for (int i = 0; i < showWidgets.size(); i++) { + showWidgets.at(i)->show(); + } + + QList labels; + labels << ui->labelHeadline << ui->labelInfo; + for (int i = 0; i < labels.size(); i++) { + QString text = labels.at(i)->text(); + replaceAppVars(text); + labels.at(i)->setText(text); + } + ui->labelChangelog->setText(generateChangelogDocument()); + + ui->checkAutoDownload->setChecked(autoDownloadEnabled(settings)); + + // Adapt buttons if release has been downloaded already + if (isDownloadFinished) { + ui->progressBar->show(); + ui->progressBar->setMaximum(1); + ui->progressBar->setValue(1); + } + + connect(feed, SIGNAL(downloadFinished()), this, SLOT(handleDownloadFinished())); + connect(feed, SIGNAL(downloadError(QString)), this, + SLOT(handleDownloadError(QString))); + connect(feed, SIGNAL(downloadProgress(qint64, qint64)), this, + SLOT(updateProgressBar(qint64, qint64))); + + connect(ui->buttonConfirm, SIGNAL(clicked()), this, SLOT(accept())); + connect(ui->actionCancel, SIGNAL(triggered()), this, SLOT(reject())); + connect(ui->actionSkip, SIGNAL(triggered()), this, SLOT(skip())); + connect(ui->checkAutoDownload, SIGNAL(toggled(bool)), this, + SLOT(autoDownloadCheckboxToggled(bool))); + + // Install buttons + if (installButtons.isEmpty()) { + ui->buttonInstall->setFocus(); + connect(ui->buttonInstall, SIGNAL(clicked()), this, SLOT(onButtonInstall())); + } else { + ui->buttonInstall->hide(); + for (int i = 0; i < installButtons.size(); i++) { + installButtons.at(i)->show(); + connect(installButtons.at(i), SIGNAL(clicked(bool)), this, + SLOT(onButtonCustomInstall())); + } + installButtons.last()->setFocus(); + } + + adjustDialogSize(); +} + +void UpdateDialog::setupChangelogUi() +{ + resetUi(); + + QList showWidgets; + showWidgets << ui->headerContainerChangelog << ui->buttonConfirm + << ui->scrollAreaChangelog; + for (int i = 0; i < showWidgets.size(); i++) { + showWidgets.at(i)->show(); + } + QList labels; + labels << ui->labelHeadlineChangelog << ui->labelInfoChangelog; + for (int i = 0; i < labels.size(); i++) { + QString text = labels.at(i)->text(); + replaceAppVars(text); + labels.at(i)->setText(text); + } + ui->labelChangelog->setText(generateChangelogDocument()); + connect(ui->buttonConfirm, SIGNAL(clicked(bool)), this, SLOT(accept())); + ui->buttonConfirm->setFocus(); + adjustDialogSize(); +} + +void UpdateDialog::setupNoUpdatesUi() +{ + resetUi(); + QList showWidgets; + showWidgets << ui->headerContainerNoUpdates << ui->buttonConfirm; + for (int i = 0; i < showWidgets.size(); i++) { + showWidgets.at(i)->show(); + } + ui->buttonConfirm->setFocus(); + + QString text = ui->labelHeadlineNoUpdates->text(); + replaceAppVars(text); + ui->labelHeadlineNoUpdates->setText(text); + + connect(ui->buttonConfirm, SIGNAL(clicked(bool)), this, SLOT(accept())); + adjustDialogSize(); +} + +void UpdateDialog::disableButtons(bool disable) +{ + QList buttons; + for (int i = 0; i < installButtons.size(); i++) { + buttons << installButtons.at(i); + } + buttons << ui->buttonCancel << ui->buttonCancelLoading << ui->buttonConfirm + << ui->buttonConfirm << ui->buttonInstall << ui->checkAutoDownload; + for (int i = 0; i < buttons.size(); i++) { + buttons.at(i)->setDisabled(disable); + } +} + +void UpdateDialog::replaceAppVars(QString& string) +{ + string.replace("%APPNAME%", QCoreApplication::applicationName()); + string.replace("%CURRENT_VERSION%", QCoreApplication::applicationVersion()); + string.replace("%UPDATE_VERSION%", latestRelease.getVersion()); +} + +QString UpdateDialog::generateChangelogDocument() +{ + QString changelog; + QList changelogReleases; + if (_minVersion.isEmpty() && _maxVersion.isEmpty()) { + changelogReleases = updates; + } else { + Release minRelease(_minVersion.isEmpty() ? QApplication::applicationVersion() + : _minVersion); + Release maxRelease(_maxVersion); + for (int i = 0; i < releases.size(); i++) { + if (minRelease < releases.at(i) + && (_maxVersion.isEmpty() || releases.at(i) <= maxRelease)) { + changelogReleases << releases.at(i); + } + } + } + for (int i = 0; i < changelogReleases.size(); i++) { + QString h2Style = "font-size: medium;"; + if (i > 0) { + h2Style.append("margin-top: 1em;"); + } + changelog.append("

" + + changelogReleases.at(i).getVersion() + "

"); + changelog.append("

" + changelogReleases.at(i).getChangelog() + "

"); + } + return changelog; +} + +void UpdateDialog::startDownload() +{ + feed->downloadRelease(latestRelease); + disableButtons(true); +} + +void UpdateDialog::startUpdate() +{ + if (QDesktopServices::openUrl(QUrl::fromLocalFile(updateFilePath))) { + done(QDialog::Accepted); + QApplication::quit(); + } else { + handleDownloadError(tr("Could not open downloaded file %1").arg(updateFilePath)); + } +} + +/* + * Private Slots + */ + +void UpdateDialog::autoDownloadCheckboxToggled(bool enabled) +{ + enableAutoDownload(enabled, settings); +} + +void UpdateDialog::handleFeedReady() +{ + // Retrieve update information + Release currentRelease(QApplication::applicationVersion()); + updates = feed->getUpdates(currentRelease); + releases = feed->getReleases(); + if (!updates.isEmpty()) { + latestRelease = updates.first(); + } + + if (type == ManualChangelog) { + setupChangelogUi(); + emit ready(); + return; + } + + // Check if an update has been downloaded previously + updateFilePath = settingsValue("updateFilePath", "", settings).toString(); + if (!updateFilePath.isEmpty() && QFile::exists(updateFilePath)) { + QString updateFileVersion = + settingsValue("updateFileVersion", "", settings).toString(); + if (updateFileVersion != latestRelease.getVersion() + || updateFileVersion == QApplication::applicationVersion()) { + QFile::remove(updateFilePath); + removeSetting("updateFilePath"); + removeSetting("updateFileVersion"); + updateFilePath = ""; + } else { + isDownloadFinished = true; + } + } + + // Check if there are any updates + if (updates.isEmpty()) { + setupNoUpdatesUi(); + return; + } + + // Automatic downloads + QString latestVersion = latestRelease.getVersion(); + bool skipRelease = + (settingsValue("skipRelease", "", settings).toString() == latestVersion); + bool autoDownload = autoDownloadEnabled(settings) && (!skipRelease); + if (autoDownload && !isDownloadFinished) { + startDownload(); + } + + // Setup UI + setupUpdateUi(); + emit ready(); +} + +void UpdateDialog::handleDownloadFinished() +{ + QTemporaryFile* file = feed->getDownloadFile(); + isDownloadFinished = true; + updateFilePath = file->fileName(); + file->setAutoRemove(false); + file->close(); + file->deleteLater(); + setSettingsValue("updateFilePath", updateFilePath, settings); + setSettingsValue("updateFileVersion", latestRelease.getVersion(), settings); + + if (accepted) { + if (acceptedInstallButton == NULL) { + startUpdate(); + } else { + emit installButtonClicked(acceptedInstallButton, updateFilePath); + } + + } else { + disableButtons(false); + } +} + +void UpdateDialog::handleDownloadError(QString message) +{ + QMessageBox* messageBox = new QMessageBox(this); + messageBox->setIcon(QMessageBox::Warning); + messageBox->setText("There was an error while downloading the update."); + messageBox->setInformativeText(message); + messageBox->show(); + done(QDialog::Rejected); +} + +void UpdateDialog::updateProgressBar(qint64 bytesReceived, qint64 bytesTotal) +{ + ui->progressBar->show(); + ui->progressBar->setMaximum(bytesTotal / 1024); + ui->progressBar->setValue(bytesReceived / 1024); +} + +void UpdateDialog::onLinkActivated(QString link) +{ + if (_openExternalLinks) { + QDesktopServices::openUrl(link); + } else { + emit linkActivated(link); + } +} + +/* + * Signals + */ +/*! \fn void Feed::ready() + * This signal is emitted when a updates are available and the UpdateDialog is + * ready to be shown with show() or exec(). + */ + +/*! \fn void Feed::installButtonClicked(QAbstractButton* button, QString filePath) + * This signal is emitted when a custom install button was clicked. + */ + +} // namespace dblsqd diff --git a/src/dblsqd/update_dialog.h b/src/dblsqd/update_dialog.h new file mode 100644 index 0000000..ce49ea8 --- /dev/null +++ b/src/dblsqd/update_dialog.h @@ -0,0 +1,106 @@ +#ifndef DBLSQD_UPDATE_DIALOG_H +#define DBLSQD_UPDATE_DIALOG_H + +#include +#include +#include +#include +#include + +#include "feed.h" +#include "ui_update_dialog.h" + +namespace dblsqd +{ + +class UpdateDialog : public QDialog +{ + Q_OBJECT + + public: + enum Type { OnUpdateAvailable, OnLastWindowClosed, Manual, ManualChangelog }; + explicit UpdateDialog(Feed* feed, int = OnUpdateAvailable, QWidget* parent = 0, + QSettings* settings = new QSettings()); + ~UpdateDialog(); + + void setIcon(QString fileName); + void setIcon(QPixmap pixmap); + void addInstallButton(QAbstractButton* button); + + void setMinVersion(QString version); + void setMaxVersion(QString version); + void setPreviousVersion(QString version); + + static bool autoDownloadEnabled(QVariant defaultValue, + QSettings* settings = new QSettings); + static bool autoDownloadEnabled(QSettings* settings = new QSettings()); + static void enableAutoDownload(bool enabled, QSettings* settings = new QSettings); + + void setOpenExternalLinks(bool open); + bool openExternalLinks(); + + signals: + void ready(); + void installButtonClicked(QAbstractButton* button, QString filePath); + void linkActivated(QString link); + + public slots: + void onButtonInstall(); + void onButtonCustomInstall(); + void skip(); + void showIfUpdatesAvailable(); + void showIfUpdatesAvailableOrQuit(); + + private: + Ui::UpdateDialog* ui; + Feed* feed; + int type; + + QSettings* settings; + void replaceAppVars(QString& string); + QString generateChangelogDocument(); + + void disableButtons(bool disable = true); + void resetUi(); + void setupLoadingUi(); + void setupUpdateUi(); + void setupChangelogUi(); + void setupNoUpdatesUi(); + void adjustDialogSize(); + + void startDownload(); + virtual void startUpdate(); + + bool accepted; + bool isDownloadFinished; + QString updateFilePath; + QList releases; + QList updates; + Release latestRelease; + QList installButtons; + QAbstractButton* acceptedInstallButton; + bool _openExternalLinks; + QString _minVersion; + QString _maxVersion; + QString _previousVersion; + + static void setSettingsValue(QString key, QVariant value, + QSettings* settings = new QSettings()); + static QVariant settingsValue(QString key, QVariant defaultValue = QVariant(), + QSettings* settings = new QSettings()); + static void removeSetting(QString key, QSettings* settings = new QSettings()); + static void setDefaultSettingsValue(QString key, QVariant value, + QSettings* settings = new QSettings()); + + private slots: + void handleFeedReady(); + void handleDownloadFinished(); + void handleDownloadError(QString); + void updateProgressBar(qint64, qint64); + void autoDownloadCheckboxToggled(bool enabled = true); + void onLinkActivated(QString link); +}; + +} // namespace dblsqd + +#endif // DBLSQD_UPDATE_DIALOG_H diff --git a/src/dblsqd/update_dialog.ui b/src/dblsqd/update_dialog.ui new file mode 100644 index 0000000..8bb40e4 --- /dev/null +++ b/src/dblsqd/update_dialog.ui @@ -0,0 +1,402 @@ + + + UpdateDialog + + + + 0 + 0 + 600 + 645 + + + + + 600 + 0 + + + + true + + + + + + + 0 + + + 6 + + + 0 + + + 0 + + + + + + 75 + true + + + + Loading update information … + + + + + + + + + + + 0 + + + 6 + + + 0 + + + 18 + + + 24 + + + 6 + + + + + + 75 + true + + + + A new version of %APPNAME% is available! + + + + + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + %APPNAME% %UPDATE_VERSION% is available (you have %CURRENT_VERSION%). +Would you like to update now? + + + true + + + + + + + + + + + 0 + + + 6 + + + 0 + + + 18 + + + + + + 75 + true + + + + Changelog for %APPNAME% + + + + + + + You are using version %CURRENT_VERSION%. + + + + + + + + + + + 0 + + + 6 + + + 0 + + + 18 + + + 24 + + + + + There are currently no updates available. + + + true + + + + + + + + 75 + true + + + + You are using %APPNAME% %CURRENT_VERSION%. + + + true + + + + + + + + + + + 0 + 0 + + + + + 0 + 150 + + + + true + + + + + 0 + 0 + 580 + 235 + + + + + 0 + 0 + + + + background:white; + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Qt::RichText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + 5 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + 24 + + + + + + + Automatically download future updates + + + + + + + + 0 + 0 + + + + + 12 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::ActionsContextMenu + + + QToolButton::MenuButtonPopup + + + Qt::ToolButtonFollowStyle + + + true + + + + + + + Cancel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Install update now + + + + + + + OK + + + + + + + + + + Remind me later + + + + + Skip this version + + + + + + checkAutoDownload + + + + diff --git a/src/gui/Browse.qml b/src/gui/Browse.qml new file mode 100644 index 0000000..1eea79f --- /dev/null +++ b/src/gui/Browse.qml @@ -0,0 +1,372 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtGraphicalEffects 1.12 + +Item { + width: parent.width; height: parent.height + clip: true + + Rectangle { + width: parent.width; height: parent.height + color: backgroundColour + } + + property bool refreshing: false + + property int buttonHeight: 25 + property int buttonWidth: 103 + property int fontMedium: 11 + + property int scrollY: 0 + + property string backgroundColour: virtualstudio.darkMode ? "#272525" : "#FAFBFB" + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + property string buttonColour: virtualstudio.darkMode ? "#494646" : "#EAECEC" + property string buttonHoverColour: virtualstudio.darkMode ? "#5B5858" : "#D3D4D4" + property string buttonPressedColour: virtualstudio.darkMode ? "#524F4F" : "#DEE0E0" + property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#40979797" + property string buttonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC" + property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC" + + function refresh() { + scrollY = studioListView.contentY; + var currentIndex = studioListView.indexAt(16 * virtualstudio.uiScale, studioListView.contentY); + if (currentIndex == -1) { + currentIndex = studioListView.indexAt(16 * virtualstudio.uiScale, studioListView.contentY + (16 * virtualstudio.uiScale)); + } + virtualstudio.refreshStudios(currentIndex) + } + + Rectangle { + z: 1 + width: parent.width; height: parent.height + color: "#40000000" + visible: refreshing + MouseArea { + anchors.fill: parent + propagateComposedEvents: false + hoverEnabled: true + preventStealing: true + } + } + + Component { + id: sectionHeading + Rectangle { + color: "transparent" + height: 72 * virtualstudio.uiScale; x: 16 * virtualstudio.uiScale; width: ListView.view.width - (2 * x) + // required property string section: section (for 5.15) + Text { + id: sectionText + //anchors.bottom: parent.bottom + y: 12 * virtualstudio.uiScale + // text: parent.section (for 5.15) + text: section + font { family: "Poppins"; pixelSize: 28 * virtualstudio.fontScale * virtualstudio.uiScale; weight: Font.Bold } + color: textColour + } + Button { + id: createButton + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: createButton.down ? "#E7E8E8" : "#F2F3F3" + border.width: 1 + border.color: createButton.down ? "#B0B5B5" : "#EAEBEB" + layer.enabled: createButton.hovered && !createButton.down + layer.effect: DropShadow { + horizontalOffset: 1 * virtualstudio.uiScale + verticalOffset: 1 * virtualstudio.uiScale + radius: 8.0 * virtualstudio.uiScale + samples: 17 + color: "#80A1A1A1" + } + } + onClicked: { virtualstudio.createStudio(); } + anchors.right: filterButton.left + anchors.rightMargin: 16 + anchors.verticalCenter: sectionText.verticalCenter + width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale + Text { + text: "Create a Studio" + font.family: "Poppins" + font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale + font.weight: Font.Bold + color: "#DB0A0A" + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + visible: section == virtualstudio.logoSection ? true : false + } + Button { + id: filterButton + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: filterButton.down ? "#E7E8E8" : "#F2F3F3" + border.width: 1 + border.color: filterButton.down ? "#B0B5B5" : "#EAEBEB" + layer.enabled: filterButton.hovered && !filterButton.down + layer.effect: DropShadow { + horizontalOffset: 1 * virtualstudio.uiScale + verticalOffset: 1 * virtualstudio.uiScale + radius: 8.0 * virtualstudio.uiScale + samples: 17 + color: "#80A1A1A1" + } + } + onClicked: { filterMenu.open(); } + anchors.right: parent.right + anchors.verticalCenter: sectionText.verticalCenter + width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale + Text { + text: "Filter Studios" + font.family: "Poppins" + font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + visible: section == virtualstudio.logoSection ? true : false + + Popup { + id: filterMenu + y: Math.round(parent.height + 8) + rightMargin: 16 * virtualstudio.uiScale + width: 210 * virtualstudio.uiScale; height: 64 * virtualstudio.uiScale + modal: false + focus: false + closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: "#F6F8F8" + border.width: 1 + border.color: "#34979797" + layer.enabled: true + layer.effect: DropShadow { + horizontalOffset: 1 * virtualstudio.uiScale + verticalOffset: 1 * virtualstudio.uiScale + radius: 8.0 * virtualstudio.uiScale + samples: 17 + color: "#80A1A1A1" + } + } + contentItem: Column { + anchors.fill: parent + CheckBox { + id: inactiveCheckbox + text: qsTr("Show my inactive Studios") + checkState: virtualstudio.showInactive ? Qt.Checked : Qt.Unchecked + onClicked: { virtualstudio.showInactive = inactiveCheckbox.checkState == Qt.Checked; + refreshing = true; + refresh(); + } + indicator: Rectangle { + implicitWidth: 16 * virtualstudio.uiScale + implicitHeight: 16 * virtualstudio.uiScale + x: inactiveCheckbox.leftPadding + y: parent.height / 2 - height / 2 + radius: 3 * virtualstudio.uiScale + border.color: inactiveCheckbox.down ? "#007AFF" : "#0062cc" + + Rectangle { + width: 10 * virtualstudio.uiScale + height: 10 * virtualstudio.uiScale + x: 3 * virtualstudio.uiScale + y: 3 * virtualstudio.uiScale + radius: 2 * virtualstudio.uiScale + color: inactiveCheckbox.down ? "#007AFF" : "#0062cc" + visible: inactiveCheckbox.checked + } + } + contentItem: Text { + text: inactiveCheckbox.text + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + leftPadding: inactiveCheckbox.indicator.width + inactiveCheckbox.spacing + } + } + CheckBox { + id: selfHostedCheckbox + text: qsTr("Show self-hosted Studios") + checkState: virtualstudio.showSelfHosted ? Qt.Checked : Qt.Unchecked + onClicked: { virtualstudio.showSelfHosted = selfHostedCheckbox.checkState == Qt.Checked; + refreshing = true; + refresh(); + } + indicator: Rectangle { + implicitWidth: 16 * virtualstudio.uiScale + implicitHeight: 16 * virtualstudio.uiScale + x: selfHostedCheckbox.leftPadding + y: parent.height / 2 - height / 2 + radius: 3 * virtualstudio.uiScale + border.color: selfHostedCheckbox.down ? "#007AFF" : "#0062CC" + + Rectangle { + width: 10 * virtualstudio.uiScale + height: 10 * virtualstudio.uiScale + x: 3 * virtualstudio.uiScale + y: 3 * virtualstudio.uiScale + radius: 2 * virtualstudio.uiScale + color: selfHostedCheckbox.down ? "#007AFF" : "#0062CC" + visible: selfHostedCheckbox.checked + } + } + contentItem: Text { + text: selfHostedCheckbox.text + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + leftPadding: selfHostedCheckbox.indicator.width + selfHostedCheckbox.spacing + } + } + } + } + } + } + } + + Component { + id: footer + Rectangle { + height: 16 * virtualstudio.uiScale + x: 16 * virtualstudio.uiScale + width: parent.width - (2 * x) + color: backgroundColour + } + } + + ListView { + id: studioListView + x:0; y: 0; width: parent.width - (2 * x); height: parent.height - 36 * virtualstudio.uiScale + spacing: 16 * virtualstudio.uiScale + header: footer + footer: footer + model: serverModel + clip: true + boundsBehavior: Flickable.StopAtBounds + delegate: Studio { + x: 16 * virtualstudio.uiScale + width: studioListView.width - (2 * x) + serverLocation: location + flagImage: flag + studioName: name + publicStudio: isPublic + manageable: isManageable + available: canConnect + connected: false + } + + section {property: "type"; criteria: ViewSection.FullString; delegate: sectionHeading } + + // Disable momentum scroll + MouseArea { + z: -1 + anchors.fill: parent + onWheel: { + // trackpad + studioListView.contentY -= wheel.pixelDelta.y; + // mouse wheel + studioListView.contentY -= wheel.angleDelta.y; + studioListView.returnToBounds(); + } + } + + Component.onCompleted: { + // Customize scroll properties on different platforms + if (Qt.platform.os == "linux" || Qt.platform.os == "osx" || + Qt.platform.os == "unix" || Qt.platform.os == "windows") { + var scrollBar = Qt.createQmlObject('import QtQuick.Controls 2.12; ScrollBar{}', + studioListView, + "dynamicSnippet1"); + scrollBar.policy = ScrollBar.AlwaysOn; + ScrollBar.vertical = scrollBar; + } + } + } + + Rectangle { + x: 0; y: parent.height - 36 * virtualstudio.uiScale; width: parent.width; height: 36 * virtualstudio.uiScale + border.color: "#33979797" + color: backgroundColour + + Button { + id: refreshButton + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: refreshButton.down ? buttonPressedColour : (refreshButton.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: refreshButton.down ? buttonPressedStroke : (refreshButton.hovered ? buttonHoverStroke : buttonStroke) + } + onClicked: { refreshing = true; refresh() } + anchors.verticalCenter: parent.verticalCenter + x: 16 * virtualstudio.uiScale + width: buttonWidth * virtualstudio.uiScale; height: buttonHeight * virtualstudio.uiScale + Text { + text: "Refresh List" + font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } + anchors {horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } + color: textColour + } + } + + Button { + id: aboutButton + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: aboutButton.down ? buttonPressedColour : (aboutButton.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: aboutButton.down ? buttonPressedStroke : (aboutButton.hovered ? buttonHoverStroke : buttonStroke) + } + onClicked: { virtualstudio.showAbout() } + anchors.verticalCenter: parent.verticalCenter + x: parent.width - (230 * virtualstudio.uiScale) + width: buttonWidth * virtualstudio.uiScale; height: buttonHeight * virtualstudio.uiScale + Text { + text: "About" + font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } + anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } + color: textColour + } + } + + Button { + id: settingsButton + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: settingsButton.down ? buttonPressedColour : (settingsButton.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: settingsButton.down ? buttonPressedStroke : (settingsButton.hovered ? buttonHoverStroke : buttonStroke) + } + onClicked: window.state = "settings" + anchors.verticalCenter: parent.verticalCenter + x: parent.width - (119 * virtualstudio.uiScale) + width: buttonWidth * virtualstudio.uiScale; height: buttonHeight * virtualstudio.uiScale + Text { + text: "Settings" + font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale} + anchors { horizontalCenter: parent.horizontalCenter; verticalCenter: parent.verticalCenter } + color: textColour + } + } + } + + Connections { + target: virtualstudio + // Need to do this to avoid layout issues with our section header. + function onNewScale() { + studioListView.positionViewAtEnd(); + studioListView.positionViewAtBeginning(); + scrollY = studioListView.contentY; + } + function onRefreshFinished(index) { + refreshing = false; + if (index == -1) { + studioListView.contentY = scrollY + } else { + studioListView.positionViewAtIndex(index, ListView.Beginning); + } + } + function onPeriodicRefresh() { refresh() } + } +} diff --git a/src/gui/Connected.qml b/src/gui/Connected.qml new file mode 100644 index 0000000..b449c46 --- /dev/null +++ b/src/gui/Connected.qml @@ -0,0 +1,94 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtGraphicalEffects 1.12 + +Item { + width: parent.width; height: parent.height + clip: true + + property bool connecting: false + + property int leftMargin: 16 + property int fontBig: 28 + property int fontMedium: 18 + + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + property real imageLightnessValue: virtualstudio.darkMode ? 1.0 : 0.0 + + Image { + x: parent.width - (49 * virtualstudio.uiScale); y: 16 * virtualstudio.uiScale + width: 32 * virtualstudio.uiScale; height: 59 * virtualstudio.uiScale + source: "logo.svg" + } + + Text { + id: heading + text: virtualstudio.connectionState + x: leftMargin * virtualstudio.uiScale; y: 34 * virtualstudio.uiScale + font { family: "Poppins"; weight: Font.Bold; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale } + color: textColour + } + + Studio { + x: parent.leftMargin * virtualstudio.uiScale; y: 96 * virtualstudio.uiScale + width: parent.width - (2 * x) + connected: true + serverLocation: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].location : "Germany - Berlin" + flagImage: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].flag : "flags/DE.svg" + studioName: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].name : "Test Studio" + publicStudio: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isPublic : false + manageable: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].isManageable : false + available: virtualstudio.currentStudio >= 0 ? serverModel[virtualstudio.currentStudio].canConnect : false + } + + Image { + id: mic + source: "mic.svg" + x: 80 * virtualstudio.uiScale; y: 250 * virtualstudio.uiScale + width: 18 * virtualstudio.uiScale; height: 28 * virtualstudio.uiScale + } + + Colorize { + anchors.fill: mic + source: mic + hue: 0 + saturation: 0 + lightness: imageLightnessValue + } + + Image { + id: headphones + source: "headphones.svg" + anchors.horizontalCenter: mic.horizontalCenter + y: 329 * virtualstudio.uiScale + width: 24 * virtualstudio.uiScale; height: 26 * virtualstudio.uiScale + } + + Colorize { + anchors.fill: headphones + source: headphones + hue: 0 + saturation: 0 + lightness: imageLightnessValue + } + + Text { + x: 120 * virtualstudio.uiScale + text: virtualstudio.audioBackend == "JACK" ? + virtualstudio.audioBackend : inputComboModel[virtualstudio.inputDevice] + font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } + anchors.verticalCenter: mic.verticalCenter + color: textColour + } + + Text { + x: 120 * virtualstudio.uiScale + text: virtualstudio.audioBackend == "JACK" ? + virtualstudio.audioBackend : outputComboModel[virtualstudio.outputDevice] + font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } + anchors.verticalCenter: headphones.verticalCenter + color: textColour + } + + //43 822 +} diff --git a/src/gui/FirstLaunch.qml b/src/gui/FirstLaunch.qml new file mode 100644 index 0000000..7356df2 --- /dev/null +++ b/src/gui/FirstLaunch.qml @@ -0,0 +1,139 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtGraphicalEffects 1.12 + +Item { + width: parent.width; height: parent.height + clip: true + + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + property string shadowColour: virtualstudio.darkMode ? "40000000" : "#80A1A1A1" + property string buttonColour: virtualstudio.darkMode ? "#565252" : "#F0F1F1" + property string buttonHoverColour: virtualstudio.darkMode ? "#6F6C6C" : "#F0F1F1" + property string buttonPressedColour: virtualstudio.darkMode ? "#494646" : "#D8D9D9" + property string buttonStroke: virtualstudio.darkMode ? "#636060" : "#DEDFDF" + property string buttonHoverStroke: virtualstudio.darkMode ? "#777575" : "#DEDFDF" + property string buttonPressedStroke: virtualstudio.darkMode ? "#6F6C6C" : "#B0B5B5" + + Image { + source: "logo.svg" + anchors.horizontalCenter: parent.horizontalCenter + y: 35 * virtualstudio.uiScale + width: 50 * virtualstudio.uiScale; height: 92 * virtualstudio.uiScale + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + y: 168 * virtualstudio.uiScale + text: "Sign in with a Virtual Studio account?" + font.family: "Poppins" + font.pixelSize: 17 * virtualstudio.fontScale * virtualstudio.uiScale + color: textColour + } + + Text { + anchors.horizontalCenter: parent.horizontalCenter + y: 219 * virtualstudio.uiScale + text: "You'll be able to change your mind later" + font.family: "Poppins" + font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale + color: textColour + } + + Button { + id: vsButton + background: Rectangle { + radius: 10 * virtualstudio.uiScale + color: vsButton.down ? buttonPressedColour : (vsButton.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: vsButton.down ? buttonPressedStroke : (vsButton.hovered ? buttonHoverStroke : buttonStroke) + layer.enabled: vsButton.hovered && !vsButton.down + layer.effect: DropShadow { + horizontalOffset: 1 * virtualstudio.uiScale + verticalOffset: 1 * virtualstudio.uiScale + radius: 8.0 * virtualstudio.uiScale + samples: 17 + color: shadowColour + } + } + onClicked: { window.state = "login"; virtualstudio.toVirtualStudio(); } + x: parent.width / 2 - (265 * virtualstudio.uiScale); y: 290 * virtualstudio.uiScale + width: 234 * virtualstudio.uiScale; height: 49 * virtualstudio.uiScale + Text { + text: "Yes" + font.family: "Poppins" + font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale + font.weight: Font.Bold + color: "#DB0A0A" + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + Text { + text: "• Connect to Virtual Studios
• Broadcast on JackTrip Radio
• Apply FX with Soundscapes" + textFormat: Text.StyledText + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + x: parent.width / 2 - (265 * virtualstudio.uiScale); + y: 355 * virtualstudio.uiScale; + width: 230 * virtualstudio.uiScale + padding: 0 + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + color: textColour + } + Image { + source: "JTVS.png" + x: parent.width / 2 - (265 * virtualstudio.uiScale); y: 420 * virtualstudio.uiScale + width: 234 * virtualstudio.uiScale; height: 201.48 * virtualstudio.uiScale; + } + + Button { + id: standardButton + background: Rectangle { + radius: 10 * virtualstudio.uiScale + color: standardButton.down ? buttonPressedColour : (standardButton.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: standardButton.down ? buttonPressedStroke : (standardButton.hovered ? buttonHoverStroke : buttonStroke) + layer.enabled: standardButton.hovered && !standardButton.down + layer.effect: DropShadow { + horizontalOffset: 1 * virtualstudio.uiScale + verticalOffset: 1 * virtualstudio.uiScale + radius: 8.0 * virtualstudio.uiScale + samples: 17 + color: shadowColour + } + } + onClicked: { window.state = "login"; virtualstudio.toStandard(); } + x: parent.width / 2 + (32 * virtualstudio.uiScale); y: 290 * virtualstudio.uiScale + width: 234 * virtualstudio.uiScale; height: 49 * virtualstudio.uiScale + Text { + text: "No" + font.family: "Poppins" + font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale + font.weight: Font.Bold + color: "#DB0A0A" + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + Image { + source: "JTOriginal.png" + x: parent.width / 2 + (32 * virtualstudio.uiScale); y: 420 * virtualstudio.uiScale + width: 234 * virtualstudio.uiScale; height: 337.37 * virtualstudio.uiScale; + } + Text { + text: virtualstudio.psiBuild ? "• Connect via IP address
• Run a local hub server
• The Standard JackTrip experience" : + "• Connect via IP address
• Run a local hub server
• The Classic JackTrip experience" + textFormat: Text.StyledText + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + x: parent.width / 2 + (32 * virtualstudio.uiScale); + y: 355 * virtualstudio.uiScale; + width: 230 * virtualstudio.uiScale + padding: 0 + wrapMode: Text.WordWrap + horizontalAlignment: Text.AlignHCenter + color: textColour + } +} diff --git a/src/gui/JTOriginal.png b/src/gui/JTOriginal.png new file mode 100644 index 0000000000000000000000000000000000000000..a6c92cd0bb228819b28afe1e76eb87f926b3acbd GIT binary patch literal 46081 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 literal 0 HcmV?d00001 diff --git a/src/gui/Login.qml b/src/gui/Login.qml new file mode 100644 index 0000000..0ed3fc0 --- /dev/null +++ b/src/gui/Login.qml @@ -0,0 +1,141 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtGraphicalEffects 1.12 + +Item { + width: parent.width; height: parent.height + clip: true + + Rectangle { + width: parent.width; height: parent.height + color: backgroundColour + } + + property bool failTextVisible: false + + property string backgroundColour: virtualstudio.darkMode ? "#272525" : "#FAFBFB" + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + property string buttonColour: virtualstudio.darkMode ? "#FAFBFB" : "#F0F1F1" + property string buttonHoverColour: virtualstudio.darkMode ? "#E9E9E9" : "#E4E5E5" + property string buttonPressedColour: virtualstudio.darkMode ? "#FAFBFB" : "#E4E5E5" + property string buttonStroke: virtualstudio.darkMode ? "#9C9C9C" : "#A4A7A7" + property string buttonTextColour: virtualstudio.darkMode ? "#272525" : "#DB0A0A" + property string buttonTextHover: virtualstudio.darkMode ? "#242222" : "#D00A0A" + property string buttonTextPressed: virtualstudio.darkMode ? "#323030" : "#D00A0A" + property string shadowColour: virtualstudio.darkMode ? "40000000" : "#80A1A1A1" + + onFailTextVisibleChanged: { + authFailedText.visible = failTextVisible; + loginButton.visible = failTextVisible || !virtualstudio.hasRefreshToken; + backButton.visible = failTextVisible || !virtualstudio.hasRefreshToken; + loggingInText.visible = !failTextVisible && virtualstudio.hasRefreshToken; + } + + Image { + id: loginLogo + source: "logo.svg" + x: parent.width / 2 - (150 * virtualstudio.uiScale); y: 110 * virtualstudio.uiScale + width: 42 * virtualstudio.uiScale; height: 76 * virtualstudio.uiScale + } + + Image { + source: virtualstudio.darkMode ? "jacktrip white.png" : "jacktrip.png" + anchors.bottom: loginLogo.bottom + x: parent.width / 2 - (88 * virtualstudio.uiScale) + width: 238 * virtualstudio.uiScale; height: 56 * virtualstudio.uiScale + } + + Text { + text: "Virtual Studio" + font.family: "Poppins" + font.pixelSize: 28 * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + y: 208 * virtualstudio.uiScale + color: textColour + } + + Text { + id: loggingInText + text: "Logging in..." + font.family: "Poppins" + font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + y: 282 * virtualstudio.uiScale + visible: virtualstudio.hasRefreshToken + color: textColour + } + + Text { + id: authFailedText + text: "Log in failed. Please try again." + font.family: "Poppins" + font.pixelSize: 16 * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + y: 272 * virtualstudio.uiScale + visible: failTextVisible + color: textColour + } + + Button { + id: loginButton + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: loginButton.down ? buttonPressedColour : (loginButton.hovered ? buttonHoverColour : buttonColour) + border.width: loginButton.down ? 1 : 0 + border.color: buttonStroke + layer.enabled: !loginButton.down + layer.effect: DropShadow { + horizontalOffset: 1 * virtualstudio.uiScale + verticalOffset: 1 * virtualstudio.uiScale + radius: 8.0 * virtualstudio.uiScale + samples: 17 + color: shadowColour + } + } + onClicked: { failTextVisible = false; virtualstudio.login() } + anchors.horizontalCenter: parent.horizontalCenter + y: 321 * virtualstudio.uiScale + width: 263 * virtualstudio.uiScale; height: 64 * virtualstudio.uiScale + Text { + text: "Sign In" + font.family: "Poppins" + font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale + font.weight: Font.Bold + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + color: loginButton.down ? buttonTextPressed : (loginButton.hovered ? buttonTextHover : buttonTextColour) + } + visible: !virtualstudio.hasRefreshToken + } + + Button { + id: backButton + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: backButton.down ? buttonPressedColour : (backButton.hovered ? buttonHoverColour : buttonColour) + border.width: backButton.down ? 1 : 0 + border.color: buttonStroke + layer.enabled: !backButton.down + layer.effect: DropShadow { + horizontalOffset: 1 * virtualstudio.uiScale + verticalOffset: 1 * virtualstudio.uiScale + radius: 8.0 * virtualstudio.uiScale + samples: 17 + color: shadowColour + } + } + onClicked: { window.state = "start" } + anchors.horizontalCenter: parent.horizontalCenter + y: 401 * virtualstudio.uiScale + width: 263 * virtualstudio.uiScale; height: 64 * virtualstudio.uiScale + Text { + text: "Back" + font.family: "Poppins" + font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + color: backButton.down ? buttonTextPressed : (backButton.hovered ? buttonTextHover : buttonTextColour) + } + visible: true + } +} diff --git a/src/gui/Poppins-Bold.ttf b/src/gui/Poppins-Bold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..00559eeb290fb8036f10633ff0640447d827b27c GIT binary patch literal 153944 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%0AC7VmlS@LZ-TEZV}oirP&oT11@@uCp8_l( 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 + + diff --git a/src/gui/ethernet.png b/src/gui/ethernet.png new file mode 100644 index 0000000000000000000000000000000000000000..c911dc4c20f13ac622f69052108c0c7bc5d3ce58 GIT binary patch literal 4922 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 literal 0 HcmV?d00001 diff --git a/src/gui/flags/AE.svg b/src/gui/flags/AE.svg new file mode 100644 index 0000000..59ddafd --- /dev/null +++ b/src/gui/flags/AE.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/AU.svg b/src/gui/flags/AU.svg new file mode 100644 index 0000000..f91b013 --- /dev/null +++ b/src/gui/flags/AU.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/BE.svg b/src/gui/flags/BE.svg new file mode 100644 index 0000000..cc1b013 --- /dev/null +++ b/src/gui/flags/BE.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/BR.svg b/src/gui/flags/BR.svg new file mode 100644 index 0000000..f4dbb02 --- /dev/null +++ b/src/gui/flags/BR.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/CA.svg b/src/gui/flags/CA.svg new file mode 100644 index 0000000..457d316 --- /dev/null +++ b/src/gui/flags/CA.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/CH.svg b/src/gui/flags/CH.svg new file mode 100644 index 0000000..498b7d1 --- /dev/null +++ b/src/gui/flags/CH.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/DE.svg b/src/gui/flags/DE.svg new file mode 100644 index 0000000..df0775b --- /dev/null +++ b/src/gui/flags/DE.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/FR.svg b/src/gui/flags/FR.svg new file mode 100644 index 0000000..9f02836 --- /dev/null +++ b/src/gui/flags/FR.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/GB.svg b/src/gui/flags/GB.svg new file mode 100644 index 0000000..4ada58a --- /dev/null +++ b/src/gui/flags/GB.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/HK.svg b/src/gui/flags/HK.svg new file mode 100644 index 0000000..284a722 --- /dev/null +++ b/src/gui/flags/HK.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/ID.svg b/src/gui/flags/ID.svg new file mode 100644 index 0000000..45d3745 --- /dev/null +++ b/src/gui/flags/ID.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/IT.svg b/src/gui/flags/IT.svg new file mode 100644 index 0000000..17b1314 --- /dev/null +++ b/src/gui/flags/IT.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/JP.svg b/src/gui/flags/JP.svg new file mode 100644 index 0000000..92eb885 --- /dev/null +++ b/src/gui/flags/JP.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/RO.svg b/src/gui/flags/RO.svg new file mode 100644 index 0000000..fabf12e --- /dev/null +++ b/src/gui/flags/RO.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/SE.svg b/src/gui/flags/SE.svg new file mode 100644 index 0000000..7ec1787 --- /dev/null +++ b/src/gui/flags/SE.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/SG.svg b/src/gui/flags/SG.svg new file mode 100644 index 0000000..c374c47 --- /dev/null +++ b/src/gui/flags/SG.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/TW.svg b/src/gui/flags/TW.svg new file mode 100644 index 0000000..c3660f1 --- /dev/null +++ b/src/gui/flags/TW.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/US.svg b/src/gui/flags/US.svg new file mode 100644 index 0000000..dc427e7 --- /dev/null +++ b/src/gui/flags/US.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/flags/ZA.svg b/src/gui/flags/ZA.svg new file mode 100644 index 0000000..1b294c9 --- /dev/null +++ b/src/gui/flags/ZA.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/gui/headphones.svg b/src/gui/headphones.svg new file mode 100644 index 0000000..fa3a213 --- /dev/null +++ b/src/gui/headphones.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/gui/jacktrip white.png b/src/gui/jacktrip white.png new file mode 100644 index 0000000000000000000000000000000000000000..7ba69b00142578859c580a97ef7868beeec0e459 GIT binary patch literal 11229 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 H#zFrFX!e(( literal 0 HcmV?d00001 diff --git a/src/gui/jacktrip.png b/src/gui/jacktrip.png new file mode 100644 index 0000000000000000000000000000000000000000..c4b998aa0cf2dff0c2120f37b82ebd13e3bf6167 GIT binary patch literal 12135 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 + + diff --git a/src/gui/leave.svg b/src/gui/leave.svg new file mode 100644 index 0000000..c44f7e7 --- /dev/null +++ b/src/gui/leave.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/gui/logo.svg b/src/gui/logo.svg new file mode 100644 index 0000000..508c81b --- /dev/null +++ b/src/gui/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/gui/mic.svg b/src/gui/mic.svg new file mode 100644 index 0000000..2568def --- /dev/null +++ b/src/gui/mic.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/gui/private.svg b/src/gui/private.svg new file mode 100644 index 0000000..c08eb3d --- /dev/null +++ b/src/gui/private.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/gui/public.svg b/src/gui/public.svg new file mode 100644 index 0000000..1407d57 --- /dev/null +++ b/src/gui/public.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/gui/qjacktrip.cpp b/src/gui/qjacktrip.cpp index 3330d25..b5a5c3f 100644 --- a/src/gui/qjacktrip.cpp +++ b/src/gui/qjacktrip.cpp @@ -34,7 +34,10 @@ #include #include "about.h" -#ifdef NO_JTVS +#ifndef NO_VS +#include "virtualstudio.h" +#endif +#ifdef PSI #include "ui_qjacktrip_novs.h" #else #include "ui_qjacktrip.h" @@ -54,7 +57,7 @@ QJackTrip::QJackTrip(int argc, QWidget* parent) : QMainWindow(parent) -#ifdef NO_JTVS +#ifdef PSI , m_ui(new Ui::QJackTrip) #else , m_ui(new Ui::QJackTripVS) @@ -72,10 +75,6 @@ QJackTrip::QJackTrip(int argc, QWidget* parent) { m_ui->setupUi(this); - QCoreApplication::setOrganizationName(QStringLiteral("jacktrip")); - QCoreApplication::setOrganizationDomain(QStringLiteral("jacktrip.org")); - QCoreApplication::setApplicationName(QStringLiteral("JackTrip")); - // Set up our debug window, and relay everything to our real cout. std::cout.rdbuf(m_debugDialog->getOutputStream()->rdbuf()); std::cerr.rdbuf(m_debugDialog->getOutputStream(1)->rdbuf()); @@ -108,6 +107,10 @@ QJackTrip::QJackTrip(int argc, QWidget* parent) About about(this); about.exec(); }); +#ifndef NO_VS + connect(m_ui->vsModeButton, &QPushButton::clicked, this, + &QJackTrip::virtualStudioMode); +#endif connect(m_ui->autoPatchComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [=]() { if (m_ui->autoPatchComboBox->currentIndex() == CLIENTFOFI @@ -234,6 +237,7 @@ QJackTrip::QJackTrip(int argc, QWidget* parent) m_ui->autoPatchGroupBox->setVisible(false); m_ui->requireAuthGroupBox->setVisible(false); m_ui->backendWarningLabel->setVisible(false); + m_ui->vsModeButton->setVisible(false); #ifdef RT_AUDIO connect(m_ui->backendComboBox, QOverload::of(&QComboBox::currentIndexChanged), @@ -324,7 +328,7 @@ QJackTrip::QJackTrip(int argc, QWidget* parent) "JACK was not found. This means that only the RtAudio backend is available " "and that JackTrip cannot be run in hub server mode."); -#ifdef NO_JTVS +#ifdef PSI QSettings settings; settings.beginGroup(QStringLiteral("Audio")); if (!settings.value(QStringLiteral("HideJackWarning"), false).toBool()) { @@ -360,7 +364,7 @@ QJackTrip::QJackTrip(int argc, QWidget* parent) settings.setValue(QStringLiteral("UsingFallback"), false); } settings.endGroup(); -#endif // NO_JTVS +#endif // PSI #else // RT_AUDIO QMessageBox msgBox; msgBox.setText( @@ -415,6 +419,37 @@ void QJackTrip::resizeEvent(QResizeEvent* event) m_ui->authDisclaimerLabel->setMinimumHeight(rect.height()); } +void QJackTrip::showEvent(QShowEvent* event) +{ + // We need to wait to load geometry until here rather than with our other settings. + // If we don't, the window geometry will be improperly set on macOS whenever the + // VirtualStudio window is shown first. + QMainWindow::showEvent(event); + if (m_firstShow) { + QSettings settings; + settings.beginGroup(QStringLiteral("Window")); + QByteArray geometry = settings.value(QStringLiteral("Geometry")).toByteArray(); + if (geometry.size() > 0) { + restoreGeometry(geometry); + } else { + // Because of hidden elements in our dialog window, it's vertical size in the + // creator is getting rediculous. Set it to something sensible by default if + // this is our first load. + this->resize(QSize(this->size().height(), 600)); + } + settings.endGroup(); + m_firstShow = false; + } +} + +#ifndef NO_VS +void QJackTrip::setVs(QSharedPointer vs) +{ + m_vs = vs; + m_ui->vsModeButton->setVisible(!m_vs.isNull()); +} +#endif + void QJackTrip::processFinished() { if (!m_jackTripRunning) { @@ -444,7 +479,7 @@ void QJackTrip::processError(const QString& errorMessage) { QMessageBox msgBox; if (errorMessage == QLatin1String("Peer Stopped")) { - // Report the other end quitting as a regular occurance rather than an error. + // Report the other end quitting as a regular occurrence rather than an error. msgBox.setText(errorMessage); msgBox.setWindowTitle(QStringLiteral("Disconnected")); } else { @@ -670,6 +705,7 @@ void QJackTrip::resetOptions() m_ui->realTimeCheckBox->setChecked(true); m_ui->ioStatsCheckBox->setChecked(false); m_ui->ioStatsSpinBox->setValue(1); + m_ui->verboseCheckBox->setChecked(false); saveSettings(); } @@ -943,6 +979,14 @@ void QJackTrip::exit() } } +#ifndef NO_VS +void QJackTrip::virtualStudioMode() +{ + this->hide(); + m_vs->show(); +} +#endif + int QJackTrip::findTab(const QString& tabName) { for (int i = 0; i < m_ui->optionsTabWidget->count(); i++) { @@ -1177,18 +1221,6 @@ void QJackTrip::loadSettings() m_ui->outClientsSpinBox->setValue( settings.value(QStringLiteral("Clients"), 1).toInt()); settings.endGroup(); - - settings.beginGroup(QStringLiteral("Window")); - QByteArray geometry = settings.value(QStringLiteral("Geometry")).toByteArray(); - if (geometry.size() > 0) { - restoreGeometry(geometry); - } else { - // Because of hidden elements in our dialog window, it's vertical size in the - // creator is getting rediculous. Set it to something sensible by default if this - // is our first load. - this->resize(QSize(this->size().width(), 600)); - } - settings.endGroup(); } void QJackTrip::saveSettings() diff --git a/src/gui/qjacktrip.h b/src/gui/qjacktrip.h index 27528a7..3410d65 100644 --- a/src/gui/qjacktrip.h +++ b/src/gui/qjacktrip.h @@ -51,13 +51,17 @@ namespace Ui { -#ifdef NO_JTVS +#ifdef PSI class QJackTrip; #else class QJackTripVS; #endif } // namespace Ui +#ifndef NO_VS +class VirtualStudio; +#endif + class QJackTrip : public QMainWindow { Q_OBJECT @@ -68,6 +72,12 @@ class QJackTrip : public QMainWindow void closeEvent(QCloseEvent* event) override; void resizeEvent(QResizeEvent* event) override; + void showEvent(QShowEvent* event) override; + +#ifndef NO_VS + enum uiModeT{UNSET, VIRTUAL_STUDIO, STANDARD}; + void setVs(QSharedPointer vs); +#endif signals: void signalExit(); @@ -88,6 +98,9 @@ class QJackTrip : public QMainWindow void start(); void stop(); void exit(); +#ifndef NO_VS + void virtualStudioMode(); +#endif private: enum runTypeT { P2P_CLIENT, P2P_SERVER, HUB_CLIENT, HUB_SERVER }; @@ -109,7 +122,7 @@ class QJackTrip : public QMainWindow QString commandLineFromCurrentOptions(); void showCommandLineMessageBox(); -#ifdef NO_JTVS +#ifdef PSI QScopedPointer m_ui; #else QScopedPointer m_ui; @@ -132,7 +145,11 @@ class QJackTrip : public QMainWindow QLabel m_autoQueueIndicator; int m_argc; bool m_hideWarning; + bool m_firstShow = true; +#ifndef NO_VS + QSharedPointer m_vs; +#endif #ifdef __APPLE__ NoNap m_noNap; #endif diff --git a/src/gui/qjacktrip.qrc b/src/gui/qjacktrip.qrc index 179c85a..ad8f864 100644 --- a/src/gui/qjacktrip.qrc +++ b/src/gui/qjacktrip.qrc @@ -4,4 +4,50 @@ about.png icon.png + + vs.qml + FirstLaunch.qml + Login.qml + Studio.qml + Browse.qml + Settings.qml + Connected.qml + Setup.qml + logo.svg + wedge.svg + wedge_inactive.svg + private.svg + public.svg + join.svg + leave.svg + cog.svg + mic.svg + ethernet.png + headphones.svg + jacktrip.png + jacktrip white.png + JTOriginal.png + JTVS.png + flags/AE.svg + flags/AU.svg + flags/BE.svg + flags/BR.svg + flags/CA.svg + flags/CH.svg + flags/DE.svg + flags/FR.svg + flags/GB.svg + flags/HK.svg + flags/ID.svg + flags/IT.svg + flags/JP.svg + flags/RO.svg + flags/SE.svg + flags/SG.svg + flags/TW.svg + flags/US.svg + flags/ZA.svg + Poppins-Bold.ttf + Poppins-Regular.ttf + diff --git a/src/gui/qjacktrip.ui b/src/gui/qjacktrip.ui index 52dc336..9044e47 100644 --- a/src/gui/qjacktrip.ui +++ b/src/gui/qjacktrip.ui @@ -308,6 +308,13 @@ To connect to a hub server you need to run as a hub client. + + + + Virtual Studio Mode + + + @@ -826,6 +833,9 @@ play from this machine. (Available in client fan out/in and full mix modes.) 2 + + 999 + 4 @@ -1470,6 +1480,12 @@ for better quality at the expense of latency. Set the broadcast queue buffer length, in packet size. + + 2 + + + 999 + 8 @@ -1811,6 +1827,7 @@ and wetness is the essence of beauty. keyBrowse credsEdit credsBrowse + vsModeButton aboutButton clientNameEdit remoteNameEdit diff --git a/src/gui/qjacktrip_novs.qrc b/src/gui/qjacktrip_novs.qrc new file mode 100644 index 0000000..179c85a --- /dev/null +++ b/src/gui/qjacktrip_novs.qrc @@ -0,0 +1,7 @@ + + + about@2x.png + about.png + icon.png + + diff --git a/src/gui/qjacktrip_novs.ui b/src/gui/qjacktrip_novs.ui index 6e6ce34..7aaced2 100644 --- a/src/gui/qjacktrip_novs.ui +++ b/src/gui/qjacktrip_novs.ui @@ -572,6 +572,13 @@ play from this machine. (Available in client fan out/in and full mix modes.) + + + + &Virtual Studio Mode + + + @@ -977,6 +984,9 @@ play from this machine. (Available in client fan out/in and full mix modes.) 2 + + 999 + 4 @@ -1470,6 +1480,12 @@ for better quality at the expense of latency. Set the broadcast queue buffer length, in packet size. + + 2 + + + 999 + 8 @@ -1814,6 +1830,7 @@ and wetness is the essence of beauty. authCheckBox usernameEdit passwordEdit + vsModeButton aboutButton clientNameEdit remoteNameEdit diff --git a/src/gui/virtualstudio.cpp b/src/gui/virtualstudio.cpp new file mode 100644 index 0000000..93df669 --- /dev/null +++ b/src/gui/virtualstudio.cpp @@ -0,0 +1,1223 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file virtualstudio.cpp + * \author Aaron Wyatt + * \date March 2022 + */ + +#include "virtualstudio.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "../jacktrip_globals.h" +#include "about.h" +#include "qjacktrip.h" + +#ifdef USE_WEAK_JACK +#include "weak_libjack.h" +#endif +#ifdef RT_AUDIO +#include "RtAudio.h" +#endif + +#ifdef _WIN32 +#include +#endif + +VirtualStudio::VirtualStudio(bool firstRun, QObject* parent) + : QObject(parent), m_showFirstRun(firstRun) +{ + QSettings settings; + m_updateChannel = + settings.value(QStringLiteral("UpdateChannel"), "stable").toString().toLower(); + settings.beginGroup(QStringLiteral("VirtualStudio")); + m_refreshToken = settings.value(QStringLiteral("RefreshToken"), "").toString(); + m_userId = settings.value(QStringLiteral("UserId"), "").toString(); + m_uiScale = settings.value(QStringLiteral("UiScale"), 1).toFloat(); + m_darkMode = settings.value(QStringLiteral("DarkMode"), false).toBool(); + m_showInactive = settings.value(QStringLiteral("ShowInactive"), false).toBool(); + m_showSelfHosted = settings.value(QStringLiteral("ShowSelfHosted"), false).toBool(); + m_showDeviceSetup = settings.value(QStringLiteral("ShowDeviceSetup"), true).toBool(); + m_showWarnings = settings.value(QStringLiteral("ShowWarnings"), true).toBool(); + settings.endGroup(); + m_previousUiScale = m_uiScale; + + // Load our font for our qml interface + QFontDatabase::addApplicationFont(QStringLiteral(":/vs/Poppins-Regular.ttf")); + QFontDatabase::addApplicationFont(QStringLiteral(":/vs/Poppins-Bold.ttf")); + + connect(&m_view, &VsQuickView::windowClose, this, &VirtualStudio::exit); + + // Set our font scaling to convert points to pixels + m_fontScale = 4.0 / 3.0; + +#ifdef RT_AUDIO + settings.beginGroup(QStringLiteral("Audio")); + m_useRtAudio = settings.value(QStringLiteral("Backend"), 0).toInt() == 1; + m_inputDevice = settings.value(QStringLiteral("InputDevice"), "").toString(); + m_outputDevice = settings.value(QStringLiteral("OutputDevice"), "").toString(); + m_bufferSize = settings.value(QStringLiteral("BufferSize"), 128).toInt(); + settings.endGroup(); + m_previousBuffer = m_bufferSize; + refreshDevices(); + m_previousInput = m_inputDevice; + m_previousOutput = m_outputDevice; +#else + m_selectableBackend = false; + + // Set our combo box models to an empty list to avoid a reference error + m_view.engine()->rootContext()->setContextProperty( + QStringLiteral("inputComboModel"), + QVariant::fromValue(QStringList(QLatin1String("")))); + m_view.engine()->rootContext()->setContextProperty( + QStringLiteral("outputComboModel"), + QVariant::fromValue(QStringList(QLatin1String("")))); +#endif + +#ifdef USE_WEAK_JACK + // Check if Jack is available + if (have_libjack() != 0) { +#ifdef RT_AUDIO + m_useRtAudio = true; + m_selectableBackend = false; +#else + // TODO: Handle this more gracefully, even if it's an unlikely scenario + qFatal("JACK not found and not built with RtAudio support."); +#endif // RT_AUDIO + } +#endif // USE_WEAK_JACK +#ifdef RT_AUDIO + m_previousUseRtAudio = m_useRtAudio; +#endif + + m_view.engine()->rootContext()->setContextProperty( + QStringLiteral("bufferComboModel"), QVariant::fromValue(m_bufferOptions)); + m_view.engine()->rootContext()->setContextProperty( + QStringLiteral("updateChannelComboModel"), + QVariant::fromValue(m_updateChannelOptions)); + m_view.engine()->rootContext()->setContextProperty(QStringLiteral("virtualstudio"), + this); + m_view.engine()->rootContext()->setContextProperty(QStringLiteral("serverModel"), + QVariant::fromValue(m_servers)); + m_view.engine()->rootContext()->setContextProperty( + QStringLiteral("backendComboModel"), + QVariant::fromValue(QStringList() + << QStringLiteral("JACK") << QStringLiteral("RtAudio"))); + m_view.setSource(QUrl(QStringLiteral("qrc:/vs/vs.qml"))); + m_view.setMinimumSize(QSize(594, 519)); + // m_view.setMaximumSize(QSize(696, 577)); + m_view.resize(696 * m_uiScale, 577 * m_uiScale); + + // Connect our timers + connect(&m_startTimer, &QTimer::timeout, this, &VirtualStudio::checkForHostname); + connect(&m_retryPeriodTimer, &QTimer::timeout, this, &VirtualStudio::endRetryPeriod); + connect(&m_refreshTimer, &QTimer::timeout, this, [&]() { + m_refreshMutex.lock(); + if (m_allowRefresh) { + m_refreshMutex.unlock(); + emit periodicRefresh(); + } else { + m_refreshMutex.unlock(); + } + }); +} + +void VirtualStudio::setStandardWindow(QSharedPointer window) +{ + m_standardWindow = window; +} + +void VirtualStudio::show() +{ + if (m_checkSsl) { + // Check our available SSL version + QString sslVersion = QSslSocket::sslLibraryVersionString(); + std::cout << "SSL Library: " << sslVersion.toStdString() << std::endl; + if (sslVersion.isEmpty()) { + QMessageBox msgBox; + msgBox.setText( + QStringLiteral("OpenSSL was not found. You will not be able to connect " + "to the Virtual Studio server.")); + msgBox.setWindowTitle(QStringLiteral("SSL Error")); + msgBox.exec(); + } + m_checkSsl = false; + } + + if (!m_showFirstRun) { + toVirtualStudio(); + } + m_view.show(); +} + +bool VirtualStudio::showFirstRun() +{ + return m_showFirstRun; +} + +bool VirtualStudio::hasRefreshToken() +{ + return !m_refreshToken.isEmpty(); +} + +QString VirtualStudio::versionString() +{ + return QLatin1String(gVersion); +} + +QString VirtualStudio::logoSection() +{ + return m_logoSection; +} + +bool VirtualStudio::selectableBackend() +{ + return m_selectableBackend; +} + +QString VirtualStudio::audioBackend() +{ + return m_useRtAudio ? QStringLiteral("RtAudio") : QStringLiteral("JACK"); +} + +void VirtualStudio::setAudioBackend(const QString& backend) +{ + if (!m_selectableBackend) { + return; + } + m_useRtAudio = (backend == QStringLiteral("RtAudio")); + emit audioBackendChanged(); +} + +int VirtualStudio::inputDevice() +{ +#ifdef RT_AUDIO + if (m_useRtAudio) { + int index = m_inputDeviceList.indexOf(m_inputDevice); + return index >= 0 ? index : 0; + } +#endif + return 0; +} + +void VirtualStudio::setInputDevice([[maybe_unused]] int device) +{ + if (!m_useRtAudio) { + return; + } +#ifdef RT_AUDIO + m_inputDevice = m_inputDeviceList.at(device); +#endif +} + +int VirtualStudio::outputDevice() +{ +#ifdef RT_AUDIO + if (m_useRtAudio) { + int index = m_outputDeviceList.indexOf(m_outputDevice); + return index >= 0 ? index : 0; + } +#endif + return 0; +} + +void VirtualStudio::setOutputDevice([[maybe_unused]] int device) +{ + if (!m_useRtAudio) { + return; + } +#ifdef RT_AUDIO + m_outputDevice = m_outputDeviceList.at(device); +#endif +} + +int VirtualStudio::bufferSize() +{ +#ifdef RT_AUDIO + if (m_useRtAudio) { + int index = m_bufferOptions.indexOf(QString::number(m_bufferSize)); + // It shouldn't be possible that our buffer size doesn't exists + // but default to 128 if something goes wrong. + return index >= 0 ? index : m_bufferOptions.indexOf(QStringLiteral("128")); + } +#endif + return 3; +} + +void VirtualStudio::setBufferSize([[maybe_unused]] int index) +{ + if (!m_useRtAudio) { + return; + } +#ifdef RT_AUDIO + m_bufferSize = m_bufferOptions.at(index).toInt(); +#endif +} + +int VirtualStudio::currentStudio() +{ + return m_currentStudio; +} + +QString VirtualStudio::connectionState() +{ + return m_connectionState; +} + +QString VirtualStudio::updateChannel() +{ + return m_updateChannel; +} + +void VirtualStudio::setUpdateChannel(const QString& channel) +{ + m_updateChannel = channel; + QSettings settings; + settings.setValue(QStringLiteral("UpdateChannel"), m_updateChannel); + emit updateChannelChanged(); +} + +bool VirtualStudio::showInactive() +{ + return m_showInactive; +} + +void VirtualStudio::setShowInactive(bool inactive) +{ + m_showInactive = inactive; + QSettings settings; + settings.beginGroup(QStringLiteral("VirtualStudio")); + settings.setValue(QStringLiteral("ShowInactive"), m_showInactive); + settings.endGroup(); +} + +bool VirtualStudio::showSelfHosted() +{ + return m_showSelfHosted; +} + +void VirtualStudio::setShowSelfHosted(bool selfHosted) +{ + m_showSelfHosted = selfHosted; + QSettings settings; + settings.beginGroup(QStringLiteral("VirtualStudio")); + settings.setValue(QStringLiteral("ShowSelfHosted"), m_showSelfHosted); + settings.endGroup(); +} + +bool VirtualStudio::showDeviceSetup() +{ + return m_showDeviceSetup; +} + +void VirtualStudio::setShowDeviceSetup(bool show) +{ + m_showDeviceSetup = show; +} + +bool VirtualStudio::showWarnings() +{ + return m_showWarnings; +} + +void VirtualStudio::setShowWarnings(bool show) +{ + m_showWarnings = show; + QSettings settings; + settings.beginGroup(QStringLiteral("VirtualStudio")); + settings.setValue(QStringLiteral("ShowWarnings"), m_showWarnings); + settings.endGroup(); + emit showWarningsChanged(); +} + +float VirtualStudio::fontScale() +{ + return m_fontScale; +} + +float VirtualStudio::uiScale() +{ + return m_uiScale; +} + +void VirtualStudio::setUiScale(float scale) +{ + m_uiScale = scale; + emit uiScaleChanged(); +} + +bool VirtualStudio::darkMode() +{ + return m_darkMode; +} + +void VirtualStudio::setDarkMode(bool dark) +{ + m_darkMode = dark; + QSettings settings; + settings.beginGroup(QStringLiteral("VirtualStudio")); + settings.setValue(QStringLiteral("DarkMode"), m_darkMode); + settings.endGroup(); + emit darkModeChanged(); +} + +bool VirtualStudio::noUpdater() +{ +#ifdef NO_UPDATER + return true; +#else + return false; +#endif +} + +bool VirtualStudio::psiBuild() +{ +#ifdef PSI + return true; +#else + return false; +#endif +} + +void VirtualStudio::toStandard() +{ + if (!m_standardWindow.isNull()) { + m_view.hide(); + m_standardWindow->show(); + } + QSettings settings; + settings.setValue(QStringLiteral("UiMode"), QJackTrip::STANDARD); + m_refreshTimer.stop(); + + if (m_showFirstRun) { + m_showFirstRun = false; + emit showFirstRunChanged(); + } +} + +void VirtualStudio::toVirtualStudio() +{ + if (!m_refreshToken.isEmpty()) { + // Attempt to refresh our virtual studio auth token + setupAuthenticator(); + + // Something about this is required for refreshing auth tokens: + // https://bugreports.qt.io/browse/QTBUG-84866 + m_authenticator->setModifyParametersFunction([](QAbstractOAuth2::Stage stage, + QVariantMap* parameters) { + if (stage == QAbstractOAuth2::Stage::RequestingAccessToken) { + QByteArray code = parameters->value(QStringLiteral("code")).toByteArray(); + (*parameters)[QStringLiteral("code")] = QUrl::fromPercentEncoding(code); + } else if (stage == QAbstractOAuth2::Stage::RequestingAuthorization) { + parameters->insert(QStringLiteral("audience"), + QStringLiteral("https://api.jacktrip.org")); + } + if (!parameters->contains("client_id")) { + parameters->insert("client_id", "cROUJag0UVKDaJ6jRAKRzlVjKVFNU39I"); + } + }); + + m_authenticator->setRefreshToken(m_refreshToken); + m_authenticator->refreshAccessToken(); + } +} + +void VirtualStudio::login() +{ + setupAuthenticator(); + m_authenticator->grant(); +} + +void VirtualStudio::logout() +{ + m_authenticator->setToken(QLatin1String("")); + m_authenticator->setRefreshToken(QLatin1String("")); + + QSettings settings; + settings.beginGroup(QStringLiteral("VirtualStudio")); + settings.remove(QStringLiteral("RefreshToken")); + settings.remove(QStringLiteral("UserId")); + settings.endGroup(); + + m_refreshTimer.stop(); + + m_refreshToken.clear(); + m_userId.clear(); + emit hasRefreshTokenChanged(); +} + +void VirtualStudio::refreshStudios(int index) +{ + getServerList(false, index); +} + +void VirtualStudio::refreshDevices() +{ +#ifdef RT_AUDIO + getDeviceList(&m_inputDeviceList, true); + getDeviceList(&m_outputDeviceList, false); + m_view.engine()->rootContext()->setContextProperty( + QStringLiteral("inputComboModel"), QVariant::fromValue(m_inputDeviceList)); + m_view.engine()->rootContext()->setContextProperty( + QStringLiteral("outputComboModel"), QVariant::fromValue(m_outputDeviceList)); + + // Make sure we keep our current settings if the device still exists + if (!m_inputDeviceList.contains(m_inputDevice)) { + m_inputDevice = QStringLiteral("(default)"); + } + if (!m_outputDeviceList.contains(m_outputDevice)) { + m_outputDevice = QStringLiteral("(default)"); + } + + emit inputDeviceChanged(); + emit outputDeviceChanged(); +#endif +} + +void VirtualStudio::revertSettings() +{ + m_uiScale = m_previousUiScale; + emit uiScaleChanged(); +#ifdef RT_AUDIO + // Restore our previous settings + m_inputDevice = m_previousInput; + m_outputDevice = m_previousOutput; + m_bufferSize = m_previousBuffer; + m_useRtAudio = m_previousUseRtAudio; + emit inputDeviceChanged(); + emit outputDeviceChanged(); + emit bufferSizeChanged(); + emit audioBackendChanged(); +#endif +} + +void VirtualStudio::applySettings() +{ + m_previousUiScale = m_uiScale; + emit newScale(); + QSettings settings; + settings.beginGroup(QStringLiteral("VirtualStudio")); + settings.setValue(QStringLiteral("UiScale"), m_uiScale); + settings.setValue(QStringLiteral("ShowDeviceSetup"), m_showDeviceSetup); + settings.endGroup(); +#ifdef RT_AUDIO + settings.beginGroup(QStringLiteral("Audio")); + settings.setValue(QStringLiteral("Backend"), m_useRtAudio ? 1 : 0); + settings.setValue(QStringLiteral("BufferSize"), m_bufferSize); + settings.setValue(QStringLiteral("InputDevice"), m_inputDevice); + settings.setValue(QStringLiteral("OutputDevice"), m_outputDevice); + settings.endGroup(); + + m_previousUseRtAudio = m_useRtAudio; + m_previousBuffer = m_bufferSize; + m_previousInput = m_inputDevice; + m_previousOutput = m_outputDevice; + + emit inputDeviceChanged(); + emit outputDeviceChanged(); +#endif +} + +void VirtualStudio::connectToStudio(int studioIndex) +{ + { + QMutexLocker locker(&m_refreshMutex); + m_allowRefresh = false; + } + m_refreshTimer.stop(); + + m_currentStudio = studioIndex; + VsServerInfo* studioInfo = static_cast(m_servers.at(m_currentStudio)); + emit currentStudioChanged(); + m_onConnectedScreen = true; + + // Check if we have an address for our server + if (studioInfo->host().isEmpty()) { + // EXPERIMENTAL CODE. (It shouldn't be possible to arrive here.) + if (studioInfo->isManageable()) { + m_connectionState = QStringLiteral("Starting Studio..."); + emit connectionStateChanged(); + + // Send a put request to start our studio + m_startedStudio = true; + QString expiration = + QDateTime::currentDateTimeUtc().addSecs(60 * 60).toString(Qt::ISODate); + QJsonObject json = {{QLatin1String("enabled"), true}, + {QLatin1String("expiresAt"), expiration}}; + QJsonDocument request = QJsonDocument(json); + + QNetworkReply* reply = m_authenticator->put( + QStringLiteral("https://app.jacktrip.org/api/servers/%1") + .arg(studioInfo->id()), + request.toJson()); + connect(reply, &QNetworkReply::finished, this, [&, reply]() { + if (reply->error() != QNetworkReply::NoError) { + m_connectionState = QStringLiteral("Unable to Start Studio"); + emit connectionStateChanged(); + } else { + QByteArray response = reply->readAll(); + QJsonDocument serverState = QJsonDocument::fromJson(response); + if (serverState.object()[QStringLiteral("status")].toString() + == QLatin1String("Starting")) { + // Start our timer to check for our hostname + m_startTimer.setInterval(5000); + m_startTimer.start(); + } + } + reply->deleteLater(); + }); + } else { + m_connectionState = QStringLiteral("Unable to Start Studio"); + emit connectionStateChanged(); + m_startedStudio = false; + } + } else { + m_startedStudio = false; + completeConnection(); + } +} + +void VirtualStudio::completeConnection() +{ + if (m_currentStudio < 0) { + return; + } + + m_jackTripRunning = true; + m_connectionState = QStringLiteral("Connecting..."); + emit connectionStateChanged(); + VsServerInfo* studioInfo = static_cast(m_servers.at(m_currentStudio)); + try { + m_jackTrip.reset(new JackTrip(JackTrip::CLIENTTOPINGSERVER, JackTrip::UDP, 2, 2, +#ifdef WAIR // wair + 0, +#endif // endwhere + 4, 1)); + m_jackTrip->setConnectDefaultAudioPorts(true); +#ifdef RT_AUDIO + if (m_useRtAudio) { + m_jackTrip->setAudiointerfaceMode(JackTrip::RTAUDIO); + m_jackTrip->setSampleRate(studioInfo->sampleRate()); + m_jackTrip->setAudioBufferSizeInSamples(m_bufferSize); + if (m_inputDevice == QLatin1String("(default)")) { + m_jackTrip->setInputDevice(""); + } else { + m_jackTrip->setInputDevice(m_inputDevice.toStdString()); + } + if (m_outputDevice == QLatin1String("(default)")) { + m_jackTrip->setOutputDevice(""); + } else { + m_jackTrip->setOutputDevice(m_outputDevice.toStdString()); + } + } +#endif + m_jackTrip->setBufferStrategy(1); + m_jackTrip->setBufferQueueLength(-500); + m_jackTrip->setPeerAddress(studioInfo->host()); + m_jackTrip->setPeerPorts(studioInfo->port()); + m_jackTrip->setPeerHandshakePort(studioInfo->port()); + + QObject::connect(m_jackTrip.data(), &JackTrip::signalProcessesStopped, this, + &VirtualStudio::processFinished, Qt::QueuedConnection); + QObject::connect(m_jackTrip.data(), &JackTrip::signalError, this, + &VirtualStudio::processError, Qt::QueuedConnection); + QObject::connect(m_jackTrip.data(), &JackTrip::signalReceivedConnectionFromPeer, + this, &VirtualStudio::receivedConnectionFromPeer, + Qt::QueuedConnection); + + // TODO: replace the following: + // m_ui->statusBar->showMessage(QStringLiteral("Waiting for Peer...")); + /* + QObject::connect(m_jackTrip.data(), &JackTrip::signalUdpWaitingTooLong, this, + &QJackTrip::udpWaitingTooLong, Qt::QueuedConnection); + QObject::connect(m_jackTrip.data(), &JackTrip::signalQueueLengthChanged, this, + &QJackTrip::queueLengthChanged, Qt::QueuedConnection);*/ + +#ifdef WAIRTOHUB // WAIR + m_jackTrip->startProcess(0); // for WAIR compatibility, ID in jack client name +#else + m_jackTrip->startProcess(); +#endif // endwhere + } catch (const std::exception& e) { + // Let the user know what our exception was. + m_connectionState = QStringLiteral("JackTrip Error"); + emit connectionStateChanged(); + + QMessageBox msgBox; + msgBox.setText(QStringLiteral("Error: ").append(e.what())); + msgBox.setWindowTitle(QStringLiteral("Doh!")); + msgBox.exec(); + + m_jackTripRunning = false; + emit disconnected(); + m_onConnectedScreen = false; + return; + } + +#ifdef __APPLE__ + m_noNap.disableNap(); +#endif +} + +void VirtualStudio::disconnect() +{ + m_connectionState = QStringLiteral("Disconnecting..."); + emit connectionStateChanged(); + m_retryPeriodTimer.stop(); + m_retryPeriod = false; + + if (m_jackTripRunning) { + if (m_startedStudio) { + VsServerInfo* studioInfo = + static_cast(m_servers.at(m_currentStudio)); + QMessageBox msgBox; + msgBox.setText(QStringLiteral("Do you want to stop the current studio?")); + msgBox.setWindowTitle(QStringLiteral("Stop Studio")); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + int ret = msgBox.exec(); + if (ret == QMessageBox::Yes) { + studioInfo->setHost(QLatin1String("")); + stopStudio(); + } + } + m_jackTrip->stop(); + } else if (m_startedStudio) { + m_startTimer.stop(); + stopStudio(); + if (!m_isExiting) { + emit disconnected(); + m_onConnectedScreen = false; + } + } else { + // How did we get here? This shouldn't be possible, but include for safety. + if (m_isExiting) { + emit signalExit(); + } else { + emit disconnected(); + m_onConnectedScreen = false; + } + } + + // Restart our studio refresh timer. + if (!m_isExiting) { + QMutexLocker locker(&m_refreshMutex); + m_allowRefresh = true; + m_refreshTimer.start(); + } +} + +void VirtualStudio::manageStudio(int studioIndex) +{ + if (studioIndex == -1) { + // We're here from a connected screen. Use our current studio. + studioIndex = m_currentStudio; + } + QUrl url = + QUrl(QStringLiteral("https://app.jacktrip.org/studios/%1") + .arg(static_cast(m_servers.at(studioIndex))->id())); + QDesktopServices::openUrl(url); +} + +void VirtualStudio::createStudio() +{ + QUrl url = QUrl(QStringLiteral("https://app.jacktrip.org/studios/create")); + QDesktopServices::openUrl(url); +} + +void VirtualStudio::showAbout() +{ + About about; + about.exec(); +} + +void VirtualStudio::exit() +{ + m_refreshTimer.stop(); + if (m_onConnectedScreen) { + m_isExiting = true; + disconnect(); + } else { + emit signalExit(); + } +} + +void VirtualStudio::slotAuthSucceded() +{ + m_refreshToken = m_authenticator->refreshToken(); + emit hasRefreshTokenChanged(); + QSettings settings; + settings.beginGroup(QStringLiteral("VirtualStudio")); + settings.setValue(QStringLiteral("RefreshToken"), m_refreshToken); + settings.endGroup(); + + settings.setValue(QStringLiteral("UiMode"), QJackTrip::VIRTUAL_STUDIO); + + if (m_userId.isEmpty()) { + getUserId(); + } else { + getSubscriptions(); + } +} + +void VirtualStudio::slotAuthFailed() +{ + emit authFailed(); +} + +void VirtualStudio::processFinished() +{ + if (m_isExiting) { + emit signalExit(); + return; + } + + if (m_retryPeriod && m_startedStudio) { + // Retry if necessary. + completeConnection(); + return; + } + + if (!m_jackTripRunning) { + return; + } + + m_jackTripRunning = false; + m_connectionState = QStringLiteral("Disconnected"); + m_jackTrip.reset(); + emit connectionStateChanged(); + emit disconnected(); + m_onConnectedScreen = false; +#ifdef __APPLE__ + m_noNap.enableNap(); +#endif +} + +void VirtualStudio::processError(const QString& errorMessage) +{ + if (!m_retryPeriod) { + QMessageBox msgBox; + if (errorMessage == QLatin1String("Peer Stopped")) { + // Report the other end quitting as a regular occurance rather than an error. + msgBox.setText(errorMessage); + msgBox.setWindowTitle(QStringLiteral("Disconnected")); + } else { + msgBox.setText(QStringLiteral("Error: ").append(errorMessage)); + msgBox.setWindowTitle(QStringLiteral("Doh!")); + } + msgBox.exec(); + } + processFinished(); +} + +void VirtualStudio::receivedConnectionFromPeer() +{ + m_connectionState = QStringLiteral("Connected"); + emit connectionStateChanged(); + std::cout << "Received connection" << std::endl; + emit connected(); +} + +void VirtualStudio::checkForHostname() +{ + if (m_currentStudio < 0) { + return; + } + + VsServerInfo* studioInfo = static_cast(m_servers.at(m_currentStudio)); + QNetworkReply* reply = m_authenticator->get( + QStringLiteral("https://app.jacktrip.org/api/servers/%1").arg(studioInfo->id())); + connect(reply, &QNetworkReply::finished, this, [&, reply, studioInfo]() { + if (reply->error() != QNetworkReply::NoError) { + m_connectionState = QStringLiteral("Unable to Start Studio"); + emit connectionStateChanged(); + + // Stop our timer + m_startTimer.stop(); + } else { + QByteArray response = reply->readAll(); + QJsonDocument serverState = QJsonDocument::fromJson(response); + if (serverState.object()[QStringLiteral("status")].toString() + == QLatin1String("Ready")) { + // Ready to connect + m_startTimer.stop(); + studioInfo->setHost( + serverState.object()[QStringLiteral("serverHost")].toString()); + studioInfo->setPort( + serverState.object()[QStringLiteral("serverPort")].toInt()); + m_retryPeriod = true; + m_retryPeriodTimer.setInterval(15000); + m_retryPeriodTimer.start(); + completeConnection(); + } + } + reply->deleteLater(); + ; + }); +} + +void VirtualStudio::endRetryPeriod() +{ + m_retryPeriod = false; + m_retryPeriodTimer.stop(); +} + +void VirtualStudio::launchBrowser(const QUrl& url) +{ + std::cout << "Launching Browser" << std::endl; + bool success = QDesktopServices::openUrl(url); + if (success) { + std::cout << "Success" << std::endl; + } else { + std::cout << "Unable to open URL" << std::endl; + } +} + +void VirtualStudio::setupAuthenticator() +{ + if (m_authenticator.isNull()) { + // Set up our authorization flow + m_authenticator.reset(new QOAuth2AuthorizationCodeFlow); + m_authenticator->setScope( + QStringLiteral("openid profile email offline_access read:servers")); + connect(m_authenticator.data(), + &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, + &VirtualStudio::launchBrowser); + + const QUrl authUri(QStringLiteral("https://auth.jacktrip.org/authorize")); + const QString clientId = QStringLiteral("cROUJag0UVKDaJ6jRAKRzlVjKVFNU39I"); + const QUrl tokenUri(QStringLiteral("https://auth.jacktrip.org/oauth/token")); + const quint16 port = 52424; + + m_authenticator->setAuthorizationUrl(authUri); + m_authenticator->setClientIdentifier(clientId); + m_authenticator->setAccessTokenUrl(tokenUri); + + m_authenticator->setModifyParametersFunction([](QAbstractOAuth2::Stage stage, + QVariantMap* parameters) { + if (stage == QAbstractOAuth2::Stage::RequestingAccessToken) { + QByteArray code = parameters->value(QStringLiteral("code")).toByteArray(); + (*parameters)[QStringLiteral("code")] = QUrl::fromPercentEncoding(code); + } else if (stage == QAbstractOAuth2::Stage::RequestingAuthorization) { + parameters->insert(QStringLiteral("audience"), + QStringLiteral("https://api.jacktrip.org")); + } + }); + + QOAuthHttpServerReplyHandler* replyHandler = + new QOAuthHttpServerReplyHandler(port, this); + replyHandler->setCallbackText(QStringLiteral( + "

\n" + "\n" + "

Virtual " + "Studio Login Successful

\n" + "

You may close this window " + "and return to the JackTrip application.

\n" + "
\n")); + m_authenticator->setReplyHandler(replyHandler); + connect(m_authenticator.data(), &QOAuth2AuthorizationCodeFlow::granted, this, + &VirtualStudio::slotAuthSucceded); + connect(m_authenticator.data(), &QOAuth2AuthorizationCodeFlow::requestFailed, + this, &VirtualStudio::slotAuthFailed); + } +} + +void VirtualStudio::getServerList(bool firstLoad, int index) +{ + { + QMutexLocker locker(&m_refreshMutex); + if (!m_allowRefresh || m_refreshInProgress) { + return; + } else { + m_refreshInProgress = true; + } + } + + // Get the serverId of the server at the top of our screen if we know it + QString topServerId; + if (index >= 0 && index < m_servers.count()) { + topServerId = static_cast(m_servers.at(index))->id(); + } + + QNetworkReply* reply = + m_authenticator->get(QStringLiteral("https://app.jacktrip.org/api/servers")); + connect(reply, &QNetworkReply::finished, this, [&, reply, topServerId, firstLoad]() { + if (reply->error() != QNetworkReply::NoError) { + std::cout << "Error: " << reply->errorString().toStdString() << std::endl; + emit authFailed(); + reply->deleteLater(); + return; + } + + QByteArray response = reply->readAll(); + QJsonDocument serverList = QJsonDocument::fromJson(response); + if (!serverList.isArray()) { + std::cout << "Error: Not an array" << std::endl; + QMutexLocker locker(&m_refreshMutex); + m_refreshInProgress = false; + emit authFailed(); + reply->deleteLater(); + return; + } + QJsonArray servers = serverList.array(); + // Divide our servers by category initially so that they're easier to sort + QList yourServers; + QList subServers; + QList pubServers; + + for (int i = 0; i < servers.count(); i++) { + if (servers.at(i)[QStringLiteral("type")].toString().contains( + QStringLiteral("JackTrip"))) { + VsServerInfo* serverInfo = new VsServerInfo(this); + serverInfo->setIsManageable( + servers.at(i)[QStringLiteral("admin")].toBool()); + QString status = servers.at(i)[QStringLiteral("status")].toString(); + bool activeStudio = status == QLatin1String("Ready"); + bool hostedStudio = servers.at(i)[QStringLiteral("managed")].toBool(); + // Only iterate through servers that we want to show + if (!m_showSelfHosted && !hostedStudio) { + continue; + } + if (!m_showInactive && !activeStudio) { + continue; + } + if (activeStudio || (serverInfo->isManageable() && m_showInactive)) { + serverInfo->setName(servers.at(i)[QStringLiteral("name")].toString()); + serverInfo->setHost( + servers.at(i)[QStringLiteral("serverHost")].toString()); + serverInfo->setStatus( + servers.at(i)[QStringLiteral("status")].toString()); + serverInfo->setPort( + servers.at(i)[QStringLiteral("serverPort")].toInt()); + serverInfo->setIsPublic( + servers.at(i)[QStringLiteral("public")].toBool()); + serverInfo->setRegion( + servers.at(i)[QStringLiteral("region")].toString()); + serverInfo->setPeriod( + servers.at(i)[QStringLiteral("period")].toInt()); + serverInfo->setSampleRate( + servers.at(i)[QStringLiteral("sampleRate")].toInt()); + serverInfo->setQueueBuffer( + servers.at(i)[QStringLiteral("queueBuffer")].toInt()); + serverInfo->setId(servers.at(i)[QStringLiteral("id")].toString()); + if (servers.at(i)[QStringLiteral("owner")].toBool()) { + yourServers.append(serverInfo); + serverInfo->setSection(VsServerInfo::YOUR_STUDIOS); + } else if (m_subscribedServers.contains(serverInfo->id())) { + subServers.append(serverInfo); + serverInfo->setSection(VsServerInfo::SUBSCRIBED_STUDIOS); + } else { + pubServers.append(serverInfo); + } + } + } + } + + std::sort(yourServers.begin(), yourServers.end(), + [](QObject* first, QObject* second) { + return static_cast(first)->name() + < static_cast(second)->name(); + }); + std::sort(subServers.begin(), subServers.end(), + [](QObject* first, QObject* second) { + return static_cast(first)->name() + < static_cast(second)->name(); + }); + std::sort(pubServers.begin(), pubServers.end(), + [](QObject* first, QObject* second) { + return static_cast(first)->name() + < static_cast(second)->name(); + }); + + // If we don't have any owned servers, move the JackTrip logo to an appropriate + // section header. + if (yourServers.isEmpty()) { + if (subServers.isEmpty()) { + m_logoSection = QStringLiteral("Public Studios"); + } else { + m_logoSection = QStringLiteral("Subscribed Studios"); + } + emit logoSectionChanged(); + } else { + m_logoSection = QStringLiteral("Your Studios"); + emit logoSectionChanged(); + } + + QMutexLocker locker(&m_refreshMutex); + // Check that we haven't tried connecting to a server between the + // request going out and the response. + if (!m_allowRefresh) { + m_refreshInProgress = false; + return; + } + m_servers.clear(); + m_servers.append(yourServers); + m_servers.append(subServers); + m_servers.append(pubServers); + m_view.engine()->rootContext()->setContextProperty( + QStringLiteral("serverModel"), QVariant::fromValue(m_servers)); + int index = -1; + if (!topServerId.isEmpty()) { + for (int i = 0; i < m_servers.count(); i++) { + if (static_cast(m_servers.at(i))->id() == topServerId) { + index = i; + break; + } + } + } + if (firstLoad) { + emit authSucceeded(); + m_refreshTimer.setInterval(10000); + m_refreshTimer.start(); + } else { + emit refreshFinished(index); + } + m_refreshInProgress = false; + + reply->deleteLater(); + }); +} + +void VirtualStudio::getUserId() +{ + QNetworkReply* reply = + m_authenticator->get(QStringLiteral("https://auth.jacktrip.org/userinfo")); + connect(reply, &QNetworkReply::finished, this, [=]() { + if (reply->error() != QNetworkReply::NoError) { + std::cout << "Error: " << reply->errorString().toStdString() << std::endl; + emit authFailed(); + reply->deleteLater(); + return; + } + + QByteArray response = reply->readAll(); + QJsonDocument userInfo = QJsonDocument::fromJson(response); + m_userId = userInfo.object()[QStringLiteral("sub")].toString(); + + QSettings settings; + settings.beginGroup(QStringLiteral("VirtualStudio")); + settings.setValue(QStringLiteral("UserId"), m_userId); + settings.endGroup(); + getSubscriptions(); + reply->deleteLater(); + }); +} + +void VirtualStudio::getSubscriptions() +{ + QNetworkReply* reply = m_authenticator->get( + QStringLiteral("https://app.jacktrip.org/api/users/%1/subscriptions") + .arg(m_userId)); + connect(reply, &QNetworkReply::finished, this, [&, reply]() { + if (reply->error() != QNetworkReply::NoError) { + std::cout << "Error: " << reply->errorString().toStdString() << std::endl; + emit authFailed(); + reply->deleteLater(); + return; + } + + QByteArray response = reply->readAll(); + QJsonDocument subscriptionList = QJsonDocument::fromJson(response); + if (!subscriptionList.isArray()) { + std::cout << "Error: Not an array" << std::endl; + emit authFailed(); + reply->deleteLater(); + return; + } + QJsonArray subscriptions = subscriptionList.array(); + for (int i = 0; i < subscriptions.count(); i++) { + m_subscribedServers.append( + subscriptions.at(i)[QStringLiteral("serverId")].toString()); + } + getServerList(true); + reply->deleteLater(); + }); +} + +#ifdef RT_AUDIO +void VirtualStudio::getDeviceList(QStringList* list, bool isInput) +{ + RtAudio audio; + list->clear(); + list->append(QStringLiteral("(default)")); + + unsigned int devices = audio.getDeviceCount(); + RtAudio::DeviceInfo info; + for (unsigned int i = 0; i < devices; i++) { + info = audio.getDeviceInfo(i); + if (info.probed == true) { + if (isInput && info.inputChannels > 0) { + list->append(QString::fromStdString(info.name)); + } else if (!isInput && info.outputChannels > 0) { + list->append(QString::fromStdString(info.name)); + } + } + } +} +#endif + +void VirtualStudio::stopStudio() +{ + if (m_currentStudio < 0) { + return; + } + + VsServerInfo* studioInfo = static_cast(m_servers.at(m_currentStudio)); + QJsonObject json = {{QLatin1String("enabled"), false}}; + QJsonDocument request = QJsonDocument(json); + studioInfo->setHost(QLatin1String("")); + QNetworkReply* reply = m_authenticator->put( + QStringLiteral("https://app.jacktrip.org/api/servers/%1").arg(studioInfo->id()), + request.toJson()); + connect(reply, &QNetworkReply::finished, this, [=]() { + if (m_isExiting && !m_jackTripRunning) { + emit signalExit(); + } + reply->deleteLater(); + }); +} + +VirtualStudio::~VirtualStudio() +{ + for (int i = 0; i < m_servers.count(); i++) { + delete m_servers.at(i); + } +} diff --git a/src/gui/virtualstudio.h b/src/gui/virtualstudio.h new file mode 100644 index 0000000..d6b2446 --- /dev/null +++ b/src/gui/virtualstudio.h @@ -0,0 +1,253 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file virtualstudio.h + * \author Aaron Wyatt + * \date March 2022 + */ + +#ifndef VIRTUALSTUDIO_H +#define VIRTUALSTUDIO_H + +#include +#include +#include +#include +#include +#include + +#include "../JackTrip.h" +#include "vsQuickView.h" +#include "vsServerInfo.h" + +#ifdef __APPLE__ +#include "NoNap.h" +#endif + +class QJackTrip; + +class VirtualStudio : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool showFirstRun READ showFirstRun NOTIFY showFirstRunChanged) + Q_PROPERTY(bool hasRefreshToken READ hasRefreshToken NOTIFY hasRefreshTokenChanged) + Q_PROPERTY(QString versionString READ versionString CONSTANT) + Q_PROPERTY(QString logoSection READ logoSection NOTIFY logoSectionChanged) + Q_PROPERTY(bool selectableBackend READ selectableBackend CONSTANT) + Q_PROPERTY(QString audioBackend READ audioBackend WRITE setAudioBackend NOTIFY + audioBackendChanged) + Q_PROPERTY( + int inputDevice READ inputDevice WRITE setInputDevice NOTIFY inputDeviceChanged) + Q_PROPERTY(int outputDevice READ outputDevice WRITE setOutputDevice NOTIFY + outputDeviceChanged) + Q_PROPERTY( + int bufferSize READ bufferSize WRITE setBufferSize NOTIFY bufferSizeChanged) + Q_PROPERTY(int currentStudio READ currentStudio NOTIFY currentStudioChanged) + Q_PROPERTY(bool showInactive READ showInactive WRITE setShowInactive NOTIFY + showInactiveChanged) + Q_PROPERTY(bool showSelfHosted READ showSelfHosted WRITE setShowSelfHosted NOTIFY + showSelfHostedChanged) + Q_PROPERTY(QString connectionState READ connectionState NOTIFY connectionStateChanged) + Q_PROPERTY(QString updateChannel READ updateChannel WRITE setUpdateChannel NOTIFY + updateChannelChanged) + Q_PROPERTY(float fontScale READ fontScale CONSTANT) + Q_PROPERTY(float uiScale READ uiScale WRITE setUiScale NOTIFY uiScaleChanged) + Q_PROPERTY(bool darkMode READ darkMode WRITE setDarkMode NOTIFY darkModeChanged) + Q_PROPERTY(bool showDeviceSetup READ showDeviceSetup WRITE setShowDeviceSetup NOTIFY + showDeviceSetupChanged) + Q_PROPERTY(bool showWarnings READ showWarnings WRITE setShowWarnings NOTIFY + showWarningsChanged) + Q_PROPERTY(bool noUpdater READ noUpdater CONSTANT) + Q_PROPERTY(bool psiBuild READ psiBuild CONSTANT) + + public: + explicit VirtualStudio(bool firstRun = false, QObject* parent = nullptr); + ~VirtualStudio() override; + + void setStandardWindow(QSharedPointer window); + void show(); + + bool showFirstRun(); + bool hasRefreshToken(); + QString versionString(); + QString logoSection(); + bool selectableBackend(); + QString audioBackend(); + void setAudioBackend(const QString& backend); + int inputDevice(); + void setInputDevice(int device); + int outputDevice(); + void setOutputDevice(int device); + int bufferSize(); + void setBufferSize(int index); + int currentStudio(); + QString connectionState(); + QString updateChannel(); + void setUpdateChannel(const QString& channel); + bool showInactive(); + void setShowInactive(bool inactive); + bool showSelfHosted(); + void setShowSelfHosted(bool selfHosted); + float fontScale(); + float uiScale(); + void setUiScale(float scale); + bool darkMode(); + void setDarkMode(bool dark); + bool showDeviceSetup(); + void setShowDeviceSetup(bool show); + bool showWarnings(); + void setShowWarnings(bool show); + bool noUpdater(); + bool psiBuild(); + + public slots: + void toStandard(); + void toVirtualStudio(); + void login(); + void logout(); + void refreshStudios(int index); + void refreshDevices(); + void revertSettings(); + void applySettings(); + void connectToStudio(int studioIndex); + void completeConnection(); + void disconnect(); + void manageStudio(int studioIndex); + void createStudio(); + void showAbout(); + void exit(); + + signals: + void authSucceeded(); + void authFailed(); + void connected(); + void disconnected(); + void refreshFinished(int index); + void showFirstRunChanged(); + void hasRefreshTokenChanged(); + void logoSectionChanged(); + void audioBackendChanged(); + void inputDeviceChanged(); + void outputDeviceChanged(); + void bufferSizeChanged(); + void currentStudioChanged(); + void showInactiveChanged(); + void showSelfHostedChanged(); + void connectionStateChanged(); + void updateChannelChanged(); + void showDeviceSetupChanged(); + void showWarningsChanged(); + void uiScaleChanged(); + void newScale(); + void darkModeChanged(); + void signalExit(); + void periodicRefresh(); + + private slots: + void slotAuthSucceded(); + void slotAuthFailed(); + void processFinished(); + void processError(const QString& errorMessage); + void receivedConnectionFromPeer(); + void checkForHostname(); + void endRetryPeriod(); + void launchBrowser(const QUrl& url); + + private: + void setupAuthenticator(); + void getServerList(bool firstLoad = false, int index = -1); + void getUserId(); + void getSubscriptions(); +#ifdef RT_AUDIO + void getDeviceList(QStringList* list, bool isInput); +#endif + void stopStudio(); + + bool m_showFirstRun = false; + bool m_checkSsl = true; + QString m_updateChannel; + QString m_refreshToken; + QString m_userId; + VsQuickView m_view; + QSharedPointer m_standardWindow; + QScopedPointer m_authenticator; + + QList m_servers; + QStringList m_subscribedServers; + QString m_logoSection = QStringLiteral("Your Studios"); + bool m_selectableBackend = true; + bool m_useRtAudio = false; + int m_currentStudio = -1; + QString m_connectionState = QStringLiteral("Connecting..."); + QScopedPointer m_jackTrip; + QTimer m_startTimer; + QTimer m_retryPeriodTimer; + bool m_startedStudio = false; + bool m_retryPeriod; + bool m_jackTripRunning = false; + + QTimer m_refreshTimer; + QMutex m_refreshMutex; + bool m_allowRefresh = true; + bool m_refreshInProgress = false; + + bool m_onConnectedScreen = false; + bool m_isExiting = false; + bool m_showInactive = false; + bool m_showSelfHosted = false; + bool m_showDeviceSetup = true; + bool m_showWarnings = true; + float m_fontScale = 1; + float m_uiScale; + float m_previousUiScale; + bool m_darkMode = false; + +#ifdef RT_AUDIO + QStringList m_inputDeviceList; + QStringList m_outputDeviceList; + QString m_inputDevice; + QString m_outputDevice; + quint16 m_bufferSize; + QString m_previousInput; + QString m_previousOutput; + quint16 m_previousBuffer; + bool m_previousUseRtAudio = false; +#endif + QStringList m_bufferOptions = {"16", "32", "64", "128", "256", "512", "1024"}; + QStringList m_updateChannelOptions = {"Stable", "Edge"}; + +#ifdef __APPLE__ + NoNap m_noNap; +#endif +}; + +#endif // VIRTUALSTUDIO_H diff --git a/src/gui/vs.qml b/src/gui/vs.qml new file mode 100644 index 0000000..1465189 --- /dev/null +++ b/src/gui/vs.qml @@ -0,0 +1,122 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +Rectangle { + property string backgroundColour: virtualstudio.darkMode ? "#272525" : "#FAFBFB" + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + + width: 696 + height: 577 + color: backgroundColour + state: virtualstudio.showFirstRun ? "start" : "login" + anchors.fill: parent + + id: window + states: [ + State { + name: "start" + PropertyChanges { target: startScreen; x: 0 } + PropertyChanges { target: loginScreen; x: window.width; failTextVisible: loginScreen.failTextVisible } + PropertyChanges { target: setupScreen; x: window.width } + PropertyChanges { target: browseScreen; x: window.width } + PropertyChanges { target: settingsScreen; x: window.width } + PropertyChanges { target: connectedScreen; x: window.width } + }, + + State { + name: "login" + PropertyChanges { target: startScreen; x: -startScreen.width } + PropertyChanges { target: loginScreen; x: 0; failTextVisible: false } + PropertyChanges { target: setupScreen; x: window.width } + PropertyChanges { target: browseScreen; x: window.width } + PropertyChanges { target: settingsScreen; x: window.width } + PropertyChanges { target: connectedScreen; x: window.width } + }, + + State { + name: "setup" + PropertyChanges { target: loginScreen; x: -loginScreen.width } + PropertyChanges { target: startScreen; x: -startScreen.width } + PropertyChanges { target: setupScreen; x: 0 } + PropertyChanges { target: browseScreen; x: window.width } + PropertyChanges { target: settingsScreen; x: window.width } + PropertyChanges { target: connectedScreen; x: window.width } + }, + + State { + name: "browse" + PropertyChanges { target: loginScreen; x: -loginScreen.width } + PropertyChanges { target: startScreen; x: -startScreen.width } + PropertyChanges { target: setupScreen; x: -setupScreen.width } + PropertyChanges { target: browseScreen; x: 0 } + PropertyChanges { target: settingsScreen; x: window.width } + PropertyChanges { target: connectedScreen; x: window.width } + }, + + State { + name: "settings" + PropertyChanges { target: loginScreen; x: -loginScreen.width } + PropertyChanges { target: startScreen; x: -startScreen.width } + PropertyChanges { target: setupScreen; x: -setupScreen.width } + PropertyChanges { target: browseScreen; x: -browseScreen.width } + PropertyChanges { target: settingsScreen; x: 0 } + PropertyChanges { target: connectedScreen; x: window.width } + }, + + State { + name: "connected" + PropertyChanges { target: loginScreen; x: -loginScreen.width } + PropertyChanges { target: startScreen; x: -startScreen.width } + PropertyChanges { target: setupScreen; x: -setupScreen.width } + PropertyChanges { target: browseScreen; x: -browseScreen.width } + PropertyChanges { target: settingsScreen; x: window.width } + PropertyChanges { target: connectedScreen; x: 0 } + } + ] + + transitions: Transition { + NumberAnimation { properties: "x"; duration: 800; easing.type: Easing.InOutQuad } + } + + FirstLaunch { + id: startScreen + } + + Setup { + id: setupScreen + } + + Browse { + id: browseScreen + } + + Login { + id: loginScreen + } + + Settings { + id: settingsScreen + } + + Connected { + id: connectedScreen + } + + Connections { + target: virtualstudio + onAuthSucceeded: { + if (virtualstudio.showDeviceSetup) { + window.state = "setup"; + } else { + window.state = "browse"; + } + } + onAuthFailed: { + loginScreen.failTextVisible = true; + } + // onConnected: { } + onDisconnected: { + window.state = "browse"; + } + } +} diff --git a/src/gui/vsQuickView.cpp b/src/gui/vsQuickView.cpp new file mode 100644 index 0000000..791104f --- /dev/null +++ b/src/gui/vsQuickView.cpp @@ -0,0 +1,47 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file vsQuickView.cpp + * \author Aaron Wyatt + * \date March 2022 + */ + +#include "vsQuickView.h" + +bool VsQuickView::event(QEvent* event) +{ + if (event->type() == QEvent::Close) { + emit windowClose(); + event->ignore(); + } + return QQuickView::event(event); +} diff --git a/src/gui/vsQuickView.h b/src/gui/vsQuickView.h new file mode 100644 index 0000000..bb9ad78 --- /dev/null +++ b/src/gui/vsQuickView.h @@ -0,0 +1,55 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file vsQuickView.h + * \author Aaron Wyatt + * \date March 2022 + */ + +#ifndef VSQUICKVIEW_H +#define VSQUICKVIEW_H + +#include + +class VsQuickView : public QQuickView +{ + Q_OBJECT + + public: + VsQuickView(QWindow* parent = nullptr) : QQuickView(parent) {} + bool event(QEvent* event) override; + + signals: + void windowClose(); +}; + +#endif // VSQUICKVIEW_H diff --git a/src/gui/vsServerInfo.cpp b/src/gui/vsServerInfo.cpp new file mode 100644 index 0000000..ec9a761 --- /dev/null +++ b/src/gui/vsServerInfo.cpp @@ -0,0 +1,211 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file vsServerInfo.cpp + * \author Aaron Wyatt + * \date March 2022 + */ + +#include "vsServerInfo.h" + +VsServerInfo::VsServerInfo(QObject* parent) : QObject(parent) {} + +VsServerInfo::serverSectionT VsServerInfo::section() +{ + return m_section; +} + +QString VsServerInfo::type() +{ + if (m_section == YOUR_STUDIOS) { + return QStringLiteral("Your Studios"); + } else if (m_section == SUBSCRIBED_STUDIOS) { + return QStringLiteral("Subscribed Studios"); + } else { + return QStringLiteral("Public Studios"); + } +} + +void VsServerInfo::setSection(serverSectionT section) +{ + m_section = section; +} + +QString VsServerInfo::name() +{ + return m_name; +} + +void VsServerInfo::setName(const QString& name) +{ + m_name = name; +} + +QString VsServerInfo::host() +{ + return m_host; +} + +QString VsServerInfo::status() +{ + return m_status; +} + +bool VsServerInfo::canConnect() +{ + return !m_host.isEmpty() && m_status == "Ready"; +} + +bool VsServerInfo::canStart() +{ +#ifdef PSI + return true; +#else + return false; +#endif +} + +void VsServerInfo::setHost(const QString& host) +{ + m_host = host; + emit canConnectChanged(); +} + +void VsServerInfo::setStatus(const QString& status) +{ + m_status = status; + emit canConnectChanged(); +} + +quint16 VsServerInfo::port() +{ + return m_port; +} + +void VsServerInfo::setPort(quint16 port) +{ + m_port = port; +} + +bool VsServerInfo::isPublic() +{ + return m_isPublic; +} + +void VsServerInfo::setIsPublic(bool isPublic) +{ + m_isPublic = isPublic; +} + +QString VsServerInfo::region() +{ + return m_region; +} + +QString VsServerInfo::flag() +{ + QStringList parts = m_region.split(QStringLiteral("-")); + if (parts.count() > 1) { + QString countryCode = parts.at(1).toUpper(); + if (countryCode == QStringLiteral("TF")) { + countryCode = QStringLiteral("TW"); + } + return QStringLiteral("flags/%1.svg").arg(countryCode); + } + // Have a fallback here + return QStringLiteral("flags/US.svg"); +} + +QString VsServerInfo::location() +{ + if (m_region.split(QStringLiteral("-")).count() > 2) { + return m_region.section(QStringLiteral("-"), 2); + } + return m_region; +} + +void VsServerInfo::setRegion(const QString& region) +{ + m_region = region; +} + +bool VsServerInfo::isManageable() +{ + return m_isManageable; +} + +void VsServerInfo::setIsManageable(bool isManageable) +{ + m_isManageable = isManageable; +} + +quint16 VsServerInfo::period() +{ + return m_period; +} + +void VsServerInfo::setPeriod(quint16 period) +{ + m_period = period; +} + +quint32 VsServerInfo::sampleRate() +{ + return m_sampleRate; +} + +void VsServerInfo::setSampleRate(quint32 sampleRate) +{ + m_sampleRate = sampleRate; +} + +quint16 VsServerInfo::queueBuffer() +{ + return m_queueBuffer; +} + +void VsServerInfo::setQueueBuffer(quint16 queueBuffer) +{ + m_queueBuffer = queueBuffer; +} + +QString VsServerInfo::id() +{ + return m_id; +} + +void VsServerInfo::setId(const QString& id) +{ + m_id = id; +} + +VsServerInfo::~VsServerInfo() = default; diff --git a/src/gui/vsServerInfo.h b/src/gui/vsServerInfo.h new file mode 100644 index 0000000..fecb849 --- /dev/null +++ b/src/gui/vsServerInfo.h @@ -0,0 +1,137 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2008-2022 Juan-Pablo Caceres, Chris Chafe. + SoundWIRE group at CCRMA, Stanford University. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. +*/ +//***************************************************************** + +/** + * \file vsServerInfo.h + * \author Aaron Wyatt + * \date March 2022 + */ + +#ifndef VSSERVERINFO_H +#define VSSERVERINFO_H + +#include + +class VsServerInfo : public QObject +{ + Q_OBJECT + + Q_PROPERTY(QString type READ type CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + // Q_PROPERTY(QString host READ host CONSTANT) + Q_PROPERTY(bool canConnect READ canConnect NOTIFY canConnectChanged) + Q_PROPERTY(bool canStart READ canStart CONSTANT) + // Q_PROPERTY(quint16 port READ port CONSTANT) + Q_PROPERTY(bool isPublic READ isPublic CONSTANT) + Q_PROPERTY(QString flag READ flag CONSTANT) + Q_PROPERTY(QString location READ location CONSTANT) + Q_PROPERTY(bool isManageable READ isManageable CONSTANT) + Q_PROPERTY(quint16 period READ period CONSTANT) + Q_PROPERTY(quint32 sampleRate READ sampleRate CONSTANT) + Q_PROPERTY(quint16 queueBuffer READ queueBuffer CONSTANT) + Q_PROPERTY(QString status READ status CONSTANT) + + public: + enum serverSectionT { YOUR_STUDIOS, SUBSCRIBED_STUDIOS, PUBLIC_STUDIOS }; + + explicit VsServerInfo(QObject* parent = nullptr); + ~VsServerInfo() override; + + serverSectionT section(); + QString type(); + void setSection(serverSectionT section); + QString name(); + void setName(const QString& name); + QString host(); + bool canConnect(); + bool canStart(); + void setHost(const QString& host); + quint16 port(); + void setPort(quint16 port); + bool isPublic(); + void setIsPublic(bool isPublic); + QString region(); + QString flag(); + QString location(); + void setRegion(const QString& region); + bool isManageable(); + void setIsManageable(bool isManageable); + quint16 period(); + void setPeriod(quint16 period); + quint32 sampleRate(); + void setSampleRate(quint32 sampleRate); + quint16 queueBuffer(); + void setQueueBuffer(quint16 queueBuffer); + QString id(); + void setId(const QString& id); + QString status(); + void setStatus(const QString& status); + + signals: + void canConnectChanged(); + + private: + serverSectionT m_section = PUBLIC_STUDIOS; + QString m_name; + QString m_host; + quint16 m_port; + bool m_isPublic; + QString m_region; + bool m_isManageable; + quint16 m_period; + quint32 m_sampleRate; + quint16 m_queueBuffer; + QString m_id; + QString m_status; + + /* Remaining JSON fields + "loopback": true, + "stereo": true, + "type": "JackTrip", + "managed": true, + "size": "c5.large", + "mixBranch": "main", + "mixCode": "SimpleMix(~maxClients).masterVolume_(1).connect.start;", + "enabled": true, + "admin": true, + "cloudId": "string", + "owner": true, + "ownerId": "string", + "status": "Ready", + "sessionId": "1636042722abcdefg", + "subStatus": "Active", + "createdAt": "2021-09-07T17:15:38Z", + "expiresAt": "2021-09-07T17:15:38Z", + "updatedAt": "2021-09-07T17:15:38Z" + */ +}; + +#endif // VSSERVERINFO_H diff --git a/src/gui/wedge.svg b/src/gui/wedge.svg new file mode 100644 index 0000000..230cdd2 --- /dev/null +++ b/src/gui/wedge.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/src/gui/wedge_inactive.svg b/src/gui/wedge_inactive.svg new file mode 100644 index 0000000..68ecbcf --- /dev/null +++ b/src/gui/wedge_inactive.svg @@ -0,0 +1,51 @@ + + + + + + + + + + diff --git a/src/jacktrip_globals.h b/src/jacktrip_globals.h index b71be81..c20c196 100644 --- a/src/jacktrip_globals.h +++ b/src/jacktrip_globals.h @@ -40,7 +40,7 @@ #include "AudioInterface.h" -constexpr const char* const gVersion = "1.5.3"; ///< JackTrip version +constexpr const char* const gVersion = "1.6.1"; ///< JackTrip version //******************************************************************************* /// \name Default Values diff --git a/src/main.cpp b/src/main.cpp index 6ff7909..ebe553b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -39,6 +39,18 @@ #include #include +#ifndef NO_UPDATER +#include "dblsqd/feed.h" +#include "dblsqd/update_dialog.h" +#endif + +#ifndef NO_VS +#include +#include + +#include "gui/virtualstudio.h" +#endif + #include "gui/qjacktrip.h" #else #include @@ -109,6 +121,8 @@ QCoreApplication* createApplication(int& argc, char* argv[]) std::exit(1); } #endif + // Turn on high DPI support. + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); return new QApplication(argc, argv); #endif // NO_GUI } else { @@ -199,6 +213,9 @@ bool isRunFromCmd() if (size >= 7 && strncmp(pname + size - 7, "cmd.exe", 7) == 0) { return true; } + if (size >= 6 && strncmp(pname + size - 6, "wt.exe", 6) == 0) { + return true; + } } else { CloseHandle(h); } @@ -214,7 +231,12 @@ int main(int argc, char* argv[]) QScopedPointer jackTrip; QScopedPointer udpHub; #ifndef NO_GUI - QScopedPointer window; + QSharedPointer window; + +#ifndef NO_VS + QSharedPointer vs; +#endif + if (qobject_cast(app.data())) { // Start the GUI if there are no command line options. #ifdef _WIN32 @@ -224,7 +246,10 @@ int main(int argc, char* argv[]) FreeConsole(); } #endif // _WIN32 - app->setApplicationName(QStringLiteral("QJackTrip")); + app->setOrganizationName(QStringLiteral("jacktrip")); + app->setOrganizationDomain(QStringLiteral("jacktrip.org")); + app->setApplicationName(QStringLiteral("JackTrip")); + app->setApplicationVersion(gVersion); QCommandLineParser parser; QCommandLineOption verboseOption(QStringList() << QStringLiteral("V") @@ -235,10 +260,55 @@ int main(int argc, char* argv[]) gVerboseFlag = true; } +#ifndef NO_VS + // Check if we need to show our first run window. + QSettings settings; + int uiMode = settings.value(QStringLiteral("UiMode"), QJackTrip::UNSET).toInt(); + QString updateChannel = settings.value(QStringLiteral("UpdateChannel"), "stable") + .toString() + .toLower(); +#endif // NO_VS window.reset(new QJackTrip(argc)); QObject::connect(window.data(), &QJackTrip::signalExit, app.data(), &QCoreApplication::quit, Qt::QueuedConnection); +#ifndef NO_VS + vs.reset(new VirtualStudio(uiMode == QJackTrip::UNSET)); + QObject::connect(vs.data(), &VirtualStudio::signalExit, app.data(), + &QCoreApplication::quit, Qt::QueuedConnection); + vs->setStandardWindow(window); + window->setVs(vs); + + if (uiMode == QJackTrip::UNSET) { + vs->show(); + } else if (uiMode == QJackTrip::VIRTUAL_STUDIO) { + vs->show(); + } else { + window->show(); + } +#else window->show(); +#endif // NO_VS + +#ifndef NO_UPDATER + // Setup auto-update feed + dblsqd::Feed* feed = 0; + QString baseUrl = + "https://raw.githubusercontent.com/jacktrip/jacktrip/dev/releases"; +#ifdef Q_OS_WIN + feed = new dblsqd::Feed(); + feed->setUrl( + QUrl(QString("%1/%2/%3-manifests.json").arg(baseUrl, updateChannel, "win"))); +#endif +#ifdef Q_OS_MACOS + feed = new dblsqd::Feed(); + feed->setUrl( + QUrl(QString("%1/%2/%3-manifests.json").arg(baseUrl, updateChannel, "mac"))); +#endif + if (feed) { + dblsqd::UpdateDialog* updateDialog = new dblsqd::UpdateDialog(feed); + updateDialog->setIcon(":/qjacktrip/icon.png"); + } +#endif // NO_UPDATER } else { #endif // NO_GUI // Otherwise use the non-GUI version, and parse our command line. diff --git a/win/CodeSignTool/CodeSignTool.sh b/win/CodeSignTool/CodeSignTool.sh new file mode 100755 index 0000000..10c4bc9 --- /dev/null +++ b/win/CodeSignTool/CodeSignTool.sh @@ -0,0 +1 @@ +java -cp "./jar/picocli-4.6.1.jar:./jar/bcprov-jdk15on-1.65.01.jar:./jar/httpclient-4.5.13.jar:./jar/json-simple-1.1.1.jar:./jar/jsign-core-3.1.jar:./jar/commons-io-2.8.0.jar:./jar/bcpkix-jdk15on-1.65.jar:./jar/code_sign_tool-1.2.2.jar:./jar/httpcore-4.4.13.jar:./jar/commons-logging-1.2.jar:./jar/log4j-api-2.17.1.jar:./jar/log4j-core-2.17.1.jar:./jar/poi-4.1.2.jar:./jar/commons-lang3-3.9.jar:./jar/commons-math3-3.6.1.jar:./jar/totp-1.0.jar:./jar/commons-codec-1.15.jar" com.ssl.code.signing.tool.CodeSignTool $@ diff --git a/win/CodeSignTool/conf/code_sign_tool.properties b/win/CodeSignTool/conf/code_sign_tool.properties new file mode 100644 index 0000000..5622896 --- /dev/null +++ b/win/CodeSignTool/conf/code_sign_tool.properties @@ -0,0 +1,4 @@ +CLIENT_ID=kaXTRACNijSWsFdRKg_KAfD3fqrBlzMbWs6TwWHwAn8 +OAUTH2_ENDPOINT=https://login.ssl.com/oauth2/token +CSC_API_ENDPOINT=https://cs.ssl.com +TSA_URL=http://ts.ssl.com \ No newline at end of file diff --git a/win/CodeSignTool/conf/log4j2.xml b/win/CodeSignTool/conf/log4j2.xml new file mode 100644 index 0000000..e3ee419 --- /dev/null +++ b/win/CodeSignTool/conf/log4j2.xml @@ -0,0 +1,28 @@ + + + + + %d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n + + + + + + ${LOG_PATTERN} + + + + + + + + + + + + + + \ No newline at end of file diff --git a/win/build_installer.bat b/win/build_installer.bat index 80f5a14..71a5340 100755 --- a/win/build_installer.bat +++ b/win/build_installer.bat @@ -44,13 +44,19 @@ set "WIXDEFINES=" for /f "tokens=*" %%a in ('%QTLIBPATH%\objdump -p jacktrip.exe ^| findstr Qt5Core.dll') do set DYNAMIC_QT=%%a if defined DYNAMIC_QT ( echo Including Qt Files - %QTBINPATH%\windeployqt jacktrip.exe + for /f "tokens=*" %%a in ('%QTLIBPATH%\objdump -p jacktrip.exe ^| findstr Qt5Qml.dll') do set VS=%%a + if defined VS ( + %QTBINPATH%\windeployqt --qmldir ..\..\src\gui jacktrip.exe + set WIXDEFINES=%WIXDEFINES% -dvs + ) else ( + %QTBINPATH%\windeployqt jacktrip.exe + ) copy "%QTLIBPATH%\libgcc_s_seh-1.dll" .\ copy "%QTLIBPATH%\libstdc++-6.dll" .\ copy "%QTLIBPATH%\libwinpthread-1.dll" .\ copy "%SSLPATH%\libcrypto-1_1-x64.dll" .\ copy "%SSLPATH%\libssl-1_1-x64.dll" .\ - set WIXDEFINES=%WIXDEFINES% -ddynamic + set WIXDEFINES=!WIXDEFINES! -ddynamic ) for /f "tokens=*" %%a in ('%QTLIBPATH%\objdump -p jacktrip.exe ^| findstr librtaudio.dll') do set RTAUDIO=%%a if defined RTAUDIO ( @@ -70,6 +76,6 @@ rem Get our version number for /f "tokens=*" %%a in ('.\jacktrip -v ^| findstr VERSION') do for %%b in (%%~a) do set VERSION=%%b for /f "tokens=1 delims=-" %%a in ("%VERSION%") do set VERSION=%%a echo Version=%VERSION% -candle.exe -dVersion=%VERSION%%WIXDEFINES% jacktrip.wxs files.wxs -light.exe -ext WixUIExtension -o JackTrip.msi jacktrip.wixobj files.wixobj +candle.exe -ext WixUIExtension -ext WixUtilExtension -dVersion=%VERSION%%WIXDEFINES% jacktrip.wxs files.wxs +light.exe -ext WixUIExtension -ext WixUtilExtension -o JackTrip.msi jacktrip.wixobj files.wixobj endlocal diff --git a/win/files.wxs b/win/files.wxs index 94c3a3c..00e2c1e 100644 --- a/win/files.wxs +++ b/win/files.wxs @@ -179,6 +179,9 @@ + + + @@ -186,6 +189,1161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -247,8 +1405,384 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win/jacktrip.wxs b/win/jacktrip.wxs index f34dab9..d6901b8 100644 --- a/win/jacktrip.wxs +++ b/win/jacktrip.wxs @@ -34,10 +34,20 @@ + + + + - + + + WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed + -- 2.30.2