From 32b064c271461194e5b9ebb8600b1e39cd1b63b5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?IOhannes=20m=20zm=C3=B6lnig=20=28Debian/GNU=29?= Date: Sun, 22 Sep 2024 22:27:49 +0200 Subject: [PATCH] New upstream version 2.4.0+ds --- CMakeLists.txt | 44 +- LICENSE.md | 29 +- LICENSES/MIT.txt | 5 +- docs/changelog.yml | 23 + jacktrip.pro | 74 +- macos/assemble_app.sh | 2 +- meson.build | 151 ++-- meson_options.txt | 2 + src/Analyzer.cpp | 3 +- src/Analyzer.h | 3 +- src/AudioInterface.cpp | 11 +- src/AudioInterface.h | 3 +- src/Meter.cpp | 3 +- src/Meter.h | 3 +- src/Monitor.cpp | 3 +- src/Monitor.h | 3 +- src/NoNap.h | 50 ++ src/NoNap.mm | 63 ++ src/Patcher.cpp | 7 + src/Regulator.cpp | 11 +- src/RtAudioInterface.cpp | 8 + src/UdpDataProtocol.cpp | 114 ++- src/UdpDataProtocol.h | 22 +- src/UserInterface.cpp | 294 +++++++ src/UserInterface.h | 111 +++ src/Volume.cpp | 3 +- src/Volume.h | 3 +- src/WaitFreeFrameBuffer.h | 4 +- src/WaitFreeRingBuffer.h | 4 +- src/gui/NoNap.h | 45 - src/gui/NoNap.mm | 58 -- src/gui/about.cpp | 2 +- src/gui/about.ui | 2 +- src/gui/qjacktrip.cpp | 59 +- src/gui/qjacktrip.h | 28 +- src/gui/qjacktrip.ui | 4 +- src/gui/qjacktrip_novs.qrc | 7 - src/gui/vsQmlClipboard.h | 26 - src/{gui/about.png => images/icon_128.png} | Bin src/{gui/about@2x.png => images/icon_256.png} | Bin src/{gui/icon.png => images/icon_32.png} | Bin src/images/images.qrc | 7 + src/jacktrip_globals.cpp | 67 +- src/jacktrip_globals.h | 5 +- src/main.cpp | 477 ++--------- src/vs/AboutWindow.qml | 145 ++++ src/{gui => vs}/AppIcon.qml | 0 src/{gui => vs}/AudioInterfaceMode.h | 3 +- src/{gui => vs}/AudioSettings.qml | 0 src/{gui => vs}/Browse.qml | 98 +-- src/{gui => vs}/ChangeDevices.qml | 2 +- src/{gui => vs}/Connected.qml | 2 +- src/{gui => vs}/CreateStudio.qml | 3 +- src/{gui => vs}/DeviceControls.qml | 0 src/{gui => vs}/DeviceControlsGroup.qml | 2 +- src/{gui => vs}/DeviceRefreshButton.qml | 0 src/{gui => vs}/DeviceWarning.qml | 18 +- src/vs/DeviceWarningModal.qml | 164 ++++ src/{gui => vs}/Failed.qml | 0 src/{gui => vs}/FeedbackSurvey.qml | 2 +- src/{gui => vs}/FirstLaunch.qml | 8 +- src/{gui => vs}/Footer.qml | 0 src/{gui => vs}/InfoTooltip.qml | 0 src/{ => vs}/JTApplication.h | 3 +- src/{gui => vs}/JTOriginal.png | Bin src/{gui => vs}/JTVS.png | Bin src/{gui => vs}/LearnMoreButton.qml | 8 + src/{gui => vs}/Login.qml | 51 +- src/{gui => vs}/Meter.qml | 0 src/{gui => vs}/MeterBars.qml | 0 src/{gui => vs}/Permissions.qml | 4 +- src/{gui => vs}/Poppins-Bold.ttf | Bin src/{gui => vs}/Poppins-Regular.ttf | Bin src/{gui => vs}/Prompt.svg | 0 src/{gui => vs}/Recommendations.qml | 3 +- src/{gui => vs}/SectionHeading.qml | 8 +- src/{gui => vs}/Settings.qml | 149 +++- src/{gui => vs}/Setup.qml | 24 +- src/{gui => vs}/Studio.qml | 0 src/{gui => vs}/VolumeSlider.qml | 0 src/{gui => vs}/Web.qml | 0 src/{gui => vs}/WebEngine.qml | 0 src/{gui => vs}/WebNull.qml | 0 src/{gui => vs}/WebSocketTransport.cpp | 0 src/{gui => vs}/WebSocketTransport.h | 0 src/{gui => vs}/WebView.qml | 0 src/{gui => vs}/check.svg | 0 src/{gui => vs}/close.svg | 0 src/{gui => vs}/cog.svg | 0 src/{gui => vs}/ethernet.svg | 0 src/{gui => vs}/expand_less.svg | 0 src/{gui => vs}/expand_more.svg | 0 src/{gui => vs}/externalMic.svg | 0 src/{gui => vs}/flags/AE.svg | 0 src/{gui => vs}/flags/AU.svg | 0 src/{gui => vs}/flags/BE.svg | 0 src/{gui => vs}/flags/BR.svg | 0 src/{gui => vs}/flags/CA.svg | 0 src/{gui => vs}/flags/CH.svg | 0 src/{gui => vs}/flags/DE.svg | 0 src/{gui => vs}/flags/FR.svg | 0 src/{gui => vs}/flags/GB.svg | 0 src/{gui => vs}/flags/HK.svg | 0 src/{gui => vs}/flags/ID.svg | 0 src/{gui => vs}/flags/IT.svg | 0 src/{gui => vs}/flags/JP.svg | 0 src/{gui => vs}/flags/RO.svg | 0 src/{gui => vs}/flags/SE.svg | 0 src/{gui => vs}/flags/SG.svg | 0 src/{gui => vs}/flags/TW.svg | 0 src/{gui => vs}/flags/US.svg | 0 src/{gui => vs}/flags/ZA.svg | 0 src/{gui => vs}/headphones.svg | 0 src/{gui => vs}/help.svg | 0 src/{gui => vs}/jacktrip white.png | Bin src/{gui => vs}/jacktrip.png | Bin src/{gui => vs}/join.svg | 0 src/{gui => vs}/language.svg | 0 src/{gui => vs}/leave.svg | 0 src/{gui => vs}/logo.svg | 0 src/{gui => vs}/loud.svg | 0 src/{gui => vs}/manage.svg | 0 src/{gui => vs}/mic.svg | 0 src/{gui => vs}/micoff.svg | 0 src/{gui => vs}/network.svg | 0 src/{gui => vs}/networkCheck.svg | 0 src/{gui => vs}/private.svg | 0 src/{gui => vs}/public.svg | 0 src/{gui => vs}/quiet.svg | 0 src/{gui => vs}/refresh.svg | 0 .../sentiment_very_dissatisfied.svg | 0 src/{gui => vs}/share.svg | 0 src/{gui => vs}/speed.svg | 0 src/{gui => vs}/star.svg | 0 src/{gui => vs}/start.svg | 0 src/{gui => vs}/video.svg | 0 src/{gui => vs}/virtualstudio.cpp | 780 ++++++++++-------- src/{gui => vs}/virtualstudio.h | 71 +- src/{gui => vs}/vs.qml | 11 + src/{gui/qjacktrip.qrc => vs/vs.qrc} | 7 +- src/{gui => vs}/vsApi.cpp | 3 +- src/{gui => vs}/vsApi.h | 3 +- src/{gui => vs}/vsAudio.cpp | 33 +- src/{gui => vs}/vsAudio.h | 35 +- src/{gui => vs}/vsAuth.cpp | 30 +- src/{gui => vs}/vsAuth.h | 9 +- src/{gui => vs}/vsConstants.h | 3 +- src/{gui => vs}/vsDeeplink.cpp | 3 +- src/{gui => vs}/vsDeeplink.h | 3 +- src/{gui => vs}/vsDevice.cpp | 11 +- src/{gui => vs}/vsDevice.h | 5 +- src/{gui => vs}/vsDeviceCodeFlow.cpp | 12 +- src/{gui => vs}/vsDeviceCodeFlow.h | 5 +- src/{gui => vs}/vsMacPermissions.h | 3 +- src/{gui => vs}/vsMacPermissions.mm | 3 +- src/{gui => vs}/vsPermissions.cpp | 3 +- src/{gui => vs}/vsPermissions.h | 3 +- src/{gui => vs}/vsPing.cpp | 3 +- src/{gui => vs}/vsPing.h | 3 +- src/{gui => vs}/vsPinger.cpp | 3 +- src/{gui => vs}/vsPinger.h | 3 +- src/vs/vsQmlClipboard.h | 56 ++ src/{gui => vs}/vsQuickView.cpp | 3 +- src/{gui => vs}/vsQuickView.h | 3 +- src/{gui => vs}/vsServerInfo.cpp | 3 +- src/{gui => vs}/vsServerInfo.h | 3 +- src/{gui => vs}/vsWebSocket.cpp | 3 +- src/{gui => vs}/vsWebSocket.h | 3 +- src/{gui => vs}/warning.svg | 0 src/{gui => vs}/wedge.svg | 0 src/{gui => vs}/wedge_inactive.svg | 0 win/build_installer.bat | 4 +- win/meson.build | 1 + 173 files changed, 2208 insertions(+), 1459 deletions(-) create mode 100644 src/NoNap.h create mode 100644 src/NoNap.mm create mode 100644 src/UserInterface.cpp create mode 100644 src/UserInterface.h delete mode 100644 src/gui/NoNap.h delete mode 100644 src/gui/NoNap.mm delete mode 100644 src/gui/qjacktrip_novs.qrc delete mode 100644 src/gui/vsQmlClipboard.h rename src/{gui/about.png => images/icon_128.png} (100%) rename src/{gui/about@2x.png => images/icon_256.png} (100%) rename src/{gui/icon.png => images/icon_32.png} (100%) create mode 100644 src/images/images.qrc create mode 100644 src/vs/AboutWindow.qml rename src/{gui => vs}/AppIcon.qml (100%) rename src/{gui => vs}/AudioInterfaceMode.h (96%) rename src/{gui => vs}/AudioSettings.qml (100%) rename src/{gui => vs}/Browse.qml (71%) rename src/{gui => vs}/ChangeDevices.qml (98%) rename src/{gui => vs}/Connected.qml (98%) rename src/{gui => vs}/CreateStudio.qml (94%) rename src/{gui => vs}/DeviceControls.qml (100%) rename src/{gui => vs}/DeviceControlsGroup.qml (99%) rename src/{gui => vs}/DeviceRefreshButton.qml (100%) rename src/{gui => vs}/DeviceWarning.qml (80%) create mode 100644 src/vs/DeviceWarningModal.qml rename src/{gui => vs}/Failed.qml (100%) rename src/{gui => vs}/FeedbackSurvey.qml (99%) rename src/{gui => vs}/FirstLaunch.qml (95%) rename src/{gui => vs}/Footer.qml (100%) rename src/{gui => vs}/InfoTooltip.qml (100%) rename src/{ => vs}/JTApplication.h (94%) rename src/{gui => vs}/JTOriginal.png (100%) rename src/{gui => vs}/JTVS.png (100%) rename src/{gui => vs}/LearnMoreButton.qml (60%) rename src/{gui => vs}/Login.qml (88%) rename src/{gui => vs}/Meter.qml (100%) rename src/{gui => vs}/MeterBars.qml (100%) rename src/{gui => vs}/Permissions.qml (98%) rename src/{gui => vs}/Poppins-Bold.ttf (100%) rename src/{gui => vs}/Poppins-Regular.ttf (100%) rename src/{gui => vs}/Prompt.svg (100%) rename src/{gui => vs}/Recommendations.qml (99%) rename src/{gui => vs}/SectionHeading.qml (97%) rename src/{gui => vs}/Settings.qml (85%) rename src/{gui => vs}/Setup.qml (92%) rename src/{gui => vs}/Studio.qml (100%) rename src/{gui => vs}/VolumeSlider.qml (100%) rename src/{gui => vs}/Web.qml (100%) rename src/{gui => vs}/WebEngine.qml (100%) rename src/{gui => vs}/WebNull.qml (100%) rename src/{gui => vs}/WebSocketTransport.cpp (100%) rename src/{gui => vs}/WebSocketTransport.h (100%) rename src/{gui => vs}/WebView.qml (100%) rename src/{gui => vs}/check.svg (100%) rename src/{gui => vs}/close.svg (100%) rename src/{gui => vs}/cog.svg (100%) rename src/{gui => vs}/ethernet.svg (100%) rename src/{gui => vs}/expand_less.svg (100%) rename src/{gui => vs}/expand_more.svg (100%) rename src/{gui => vs}/externalMic.svg (100%) rename src/{gui => vs}/flags/AE.svg (100%) rename src/{gui => vs}/flags/AU.svg (100%) rename src/{gui => vs}/flags/BE.svg (100%) rename src/{gui => vs}/flags/BR.svg (100%) rename src/{gui => vs}/flags/CA.svg (100%) rename src/{gui => vs}/flags/CH.svg (100%) rename src/{gui => vs}/flags/DE.svg (100%) rename src/{gui => vs}/flags/FR.svg (100%) rename src/{gui => vs}/flags/GB.svg (100%) rename src/{gui => vs}/flags/HK.svg (100%) rename src/{gui => vs}/flags/ID.svg (100%) rename src/{gui => vs}/flags/IT.svg (100%) rename src/{gui => vs}/flags/JP.svg (100%) rename src/{gui => vs}/flags/RO.svg (100%) rename src/{gui => vs}/flags/SE.svg (100%) rename src/{gui => vs}/flags/SG.svg (100%) rename src/{gui => vs}/flags/TW.svg (100%) rename src/{gui => vs}/flags/US.svg (100%) rename src/{gui => vs}/flags/ZA.svg (100%) rename src/{gui => vs}/headphones.svg (100%) rename src/{gui => vs}/help.svg (100%) rename src/{gui => vs}/jacktrip white.png (100%) rename src/{gui => vs}/jacktrip.png (100%) rename src/{gui => vs}/join.svg (100%) rename src/{gui => vs}/language.svg (100%) rename src/{gui => vs}/leave.svg (100%) rename src/{gui => vs}/logo.svg (100%) rename src/{gui => vs}/loud.svg (100%) rename src/{gui => vs}/manage.svg (100%) rename src/{gui => vs}/mic.svg (100%) rename src/{gui => vs}/micoff.svg (100%) rename src/{gui => vs}/network.svg (100%) rename src/{gui => vs}/networkCheck.svg (100%) rename src/{gui => vs}/private.svg (100%) rename src/{gui => vs}/public.svg (100%) rename src/{gui => vs}/quiet.svg (100%) rename src/{gui => vs}/refresh.svg (100%) rename src/{gui => vs}/sentiment_very_dissatisfied.svg (100%) rename src/{gui => vs}/share.svg (100%) rename src/{gui => vs}/speed.svg (100%) rename src/{gui => vs}/star.svg (100%) rename src/{gui => vs}/start.svg (100%) rename src/{gui => vs}/video.svg (100%) rename src/{gui => vs}/virtualstudio.cpp (72%) rename src/{gui => vs}/virtualstudio.h (88%) rename src/{gui => vs}/vs.qml (98%) rename src/{gui/qjacktrip.qrc => vs/vs.qrc} (95%) rename src/{gui => vs}/vsApi.cpp (98%) rename src/{gui => vs}/vsApi.h (95%) rename src/{gui => vs}/vsAudio.cpp (98%) rename src/{gui => vs}/vsAudio.h (94%) rename src/{gui => vs}/vsAuth.cpp (90%) rename src/{gui => vs}/vsAuth.h (93%) rename src/{gui => vs}/vsConstants.h (94%) rename src/{gui => vs}/vsDeeplink.cpp (98%) rename src/{gui => vs}/vsDeeplink.h (97%) rename src/{gui => vs}/vsDevice.cpp (98%) rename src/{gui => vs}/vsDevice.h (95%) rename src/{gui => vs}/vsDeviceCodeFlow.cpp (96%) rename src/{gui => vs}/vsDeviceCodeFlow.h (95%) rename src/{gui => vs}/vsMacPermissions.h (94%) rename src/{gui => vs}/vsMacPermissions.mm (97%) rename src/{gui => vs}/vsPermissions.cpp (94%) rename src/{gui => vs}/vsPermissions.h (95%) rename src/{gui => vs}/vsPing.cpp (95%) rename src/{gui => vs}/vsPing.h (94%) rename src/{gui => vs}/vsPinger.cpp (98%) rename src/{gui => vs}/vsPinger.h (96%) create mode 100644 src/vs/vsQmlClipboard.h rename src/{gui => vs}/vsQuickView.cpp (95%) rename src/{gui => vs}/vsQuickView.h (94%) rename src/{gui => vs}/vsServerInfo.cpp (98%) rename src/{gui => vs}/vsServerInfo.h (98%) rename src/{gui => vs}/vsWebSocket.cpp (97%) rename src/{gui => vs}/vsWebSocket.h (95%) rename src/{gui => vs}/warning.svg (100%) rename src/{gui => vs}/wedge.svg (100%) rename src/{gui => vs}/wedge_inactive.svg (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index a4e80a8..e4db93d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,11 +31,11 @@ if (psi) set(novs TRUE) endif () +set(QRC_FILE "src/images/images.qrc") if (novs) add_compile_definitions(NO_VS) - set(QRC_FILE "src/gui/qjacktrip_novs.qrc") else () - set(QRC_FILE "src/gui/qjacktrip.qrc") + set(QRC_FILE "src/vs/vs.qrc") endif () if (vsftux) @@ -180,37 +180,39 @@ if (NOT nogui) src/gui/textbuf.cpp src/gui/vuMeter.cpp src/Meter.cpp + src/UserInterface.cpp ) if (NOT novs) set (qjacktrip_SRC ${qjacktrip_SRC} - src/gui/virtualstudio.cpp - src/gui/vsApi.cpp - src/gui/vsAuth.cpp - src/gui/vsDeviceCodeFlow.cpp - src/gui/vsDeeplink.cpp - src/gui/vsQuickView.cpp - src/gui/vsServerInfo.cpp - src/gui/vsPing.cpp - src/gui/vsPinger.cpp - src/gui/vsDevice.cpp - src/gui/vsAudio.cpp - src/gui/vsWebSocket.cpp - src/gui/vsPermissions.cpp - src/gui/qjacktrip.qrc + src/vs/virtualstudio.cpp + src/vs/vsApi.cpp + src/vs/vsAuth.cpp + src/vs/vsDeviceCodeFlow.cpp + src/vs/vsDeeplink.cpp + src/vs/vsQuickView.cpp + src/vs/vsServerInfo.cpp + src/vs/vsPing.cpp + src/vs/vsPinger.cpp + src/vs/vsDevice.cpp + src/vs/vsAudio.cpp + src/vs/vsWebSocket.cpp + src/vs/vsPermissions.cpp + src/vs/vs.qrc + src/images/images.qrc src/Analyzer.cpp src/Monitor.cpp src/Volume.cpp src/Tone.cpp # Need to include this for AUTOMOC to do its thing - src/JTApplication.h - src/gui/vsQmlClipboard.h + src/vs/JTApplication.h + src/vs/vsQmlClipboard.h ) if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set (qjacktrip_SRC ${qjacktrip_SRC} src/gui/vsMacPermissions.mm) + set (qjacktrip_SRC ${qjacktrip_SRC} src/vs/vsMacPermissions.mm) endif () else () - set (qjacktrip_SRC ${qjacktrip_SRC} src/gui/qjacktrip_novs.qrc) + set (qjacktrip_SRC ${qjacktrip_SRC} src/images/images.qrc) endif () if (NOT noupdater) @@ -232,7 +234,7 @@ if (NOT nogui) file(WRITE "win/qjacktrip.rc" "${RC_CONTENTS}") set (qjacktrip_SRC ${qjacktrip_SRC} win/qjacktrip.rc) elseif (${CMAKE_SYSTEM_NAME} MATCHES "Darwin") - set (qjacktrip_SRC ${qjacktrip_SRC} src/gui/NoNap.mm) + set (qjacktrip_SRC ${qjacktrip_SRC} src/NoNap.mm) set (CMAKE_C_FLAGS "-x objective-c") set (CMAKE_EXE_LINKER_FLAGS "-framework Foundation") if (NOT novs) diff --git a/LICENSE.md b/LICENSE.md index 0169495..3723a4f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,20 +1,28 @@ # JackTrip License -Copyright © 2008-2020 Juan-Pablo Caceres, Chris Chafe. +Copyright © 2008-2024 Juan-Pablo Caceres, Chris Chafe, et al. SoundWIRE group at CCRMA, Stanford University. +Virtual Studio interface and integration, +Copyright © 2022-2024 JackTrip Labs, Inc. + Classic mode graphical user interface originally released as QJackTrip, Copyright © 2020 Aaron Wyatt -Virtual Studio interface and integration -Copyright © 2022-2023 JackTrip Labs, Inc. +The JackTrip project including Virtual Studio interface and integration +is open source distributed under the MIT license. The Classic mode +graphical interface is open source distributed under a GPL license. + +JackTrip uses the Qt library. Qt's source code can be downloaded from +[https://download.qt.io/official_releases/qt/](https://download.qt.io/official_releases/qt/). -JackTrip project consists of files under MIT and GPL licenses, indicated in the -header of individual files. Early versions of JackTrip were licensed under MIT. +Unsigned builds provided on GitHub's Releases page include the Classic +mode graphical interface and use an open source distribution of Qt. +These are distributed under a GPL license. -JackTrip uses Qt library throughout the project so the resulting binaries are -also subject to Qt's license. The builds provided on GitHub's Releases page use -open source distribution of Qt, licensed under LGPL. +Signed builds for Windows and Mac provided by JackTrip Labs do not +include the Classic mode graphical interface and use a commercial +Qt license. These are distributed under a MIT license. Windows builds of JackTrip may include support for ASIO. ASIO is a trademark and software of Steinberg Media Technologies GmbH. @@ -22,6 +30,5 @@ ASIO is a trademark and software of Steinberg Media Technologies GmbH. Using JackTrip to join Virtual Studios on Windows computers may use AVC (h264) video encoders and decoders subject to the AVC Patent Portfolio License. -The text of individual licenses is provided in the `LICENSES/` folder. Qt's -source code can be downloaded from -[https://download.qt.io/official_releases/qt/](https://download.qt.io/official_releases/qt/). +The text of individual licenses is provided in the `LICENSES/` folder. + diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt index a843d97..adb686a 100644 --- a/LICENSES/MIT.txt +++ b/LICENSES/MIT.txt @@ -1,6 +1,9 @@ - Copyright (c) 2020 Juan-Pablo Caceres, Chris Chafe. + Copyright (c) 2008-2024 Juan-Pablo Caceres, Chris Chafe, et al. SoundWIRE group at CCRMA, Stanford University. + Virtual Studio interface and integration + Copyright (c) 2022-2024 JackTrip Labs, Inc. + 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 diff --git a/docs/changelog.yml b/docs/changelog.yml index 4f4be6d..105c429 100644 --- a/docs/changelog.yml +++ b/docs/changelog.yml @@ -1,3 +1,26 @@ +- Version: "2.4.0" + Date: 2024-09-13 + Description: + - (addded) qWave Quality of Service for Windows users + - (addded) VS Mode warning when speakers selected for output + - (addded) VS Mode dialog when connecting with audio warning + - (updated) PLC auto headroom tweaks to improve audio quality + - (updated) Increased UDP timeout from 30 to 50 milliseconds + - (updated) MIT license for VS mode interface and integration + - (updated) Removed classic mode from JackTrip Labs builds + - (updated) ASIO is now only enabled for JackTrip Labs builds + - (updated) VS Mode now only supports buffer strategy 3 (PLC) + - (updated) VS Mode removed button click to create first studio + - (updated) VS Mode updated to use new studio creation interface + - (updated) VS Mode updated to use new user profile interface + - (updated) VS Mode changed red text color for buttons to black + - (updated) VS Mode improved error handling for login screen + - (updated) VS Mode improved messaging while loading studios + - (fixed) VS Mode possible failures when loading studios + - (fixed) VS Mode studio refresh updated to avoid jumpiness + - (fixed) VS Mode blacklisted iPhone microphone device + - (fixed) Race condition in automatic patching for JACK + - (fixed) Missing files in Linux binary zip file - Version: "2.3.1" Date: 2024-07-26 Description: diff --git a/jacktrip.pro b/jacktrip.pro index c3f9fce..60c2198 100644 --- a/jacktrip.pro +++ b/jacktrip.pro @@ -258,21 +258,21 @@ HEADERS += src/DataProtocol.h \ src/gui/textbuf.h \ src/gui/vuMeter.h !novs { - HEADERS += src/gui/virtualstudio.h \ - src/gui/vsApi.h \ - src/gui/vsAuth.h \ - src/gui/vsDeviceCodeFlow.h \ - src/gui/vsDeeplink.h \ - src/gui/vsDevice.h \ - src/gui/vsAudio.h \ - src/gui/vsServerInfo.h \ - src/gui/vsQuickView.h \ - src/gui/vsWebSocket.h \ - src/gui/vsPermissions.h \ - src/gui/vsPinger.h \ - src/gui/vsPing.h \ - src/gui/vsQmlClipboard.h \ - src/JTApplication.h + HEADERS += src/vs/virtualstudio.h \ + src/vs/vsApi.h \ + src/vs/vsAuth.h \ + src/vs/vsDeviceCodeFlow.h \ + src/vs/vsDeeplink.h \ + src/vs/vsDevice.h \ + src/vs/vsAudio.h \ + src/vs/vsServerInfo.h \ + src/vs/vsQuickView.h \ + src/vs/vsWebSocket.h \ + src/vs/vsPermissions.h \ + src/vs/vsPinger.h \ + src/vs/vsPing.h \ + src/vs/vsQmlClipboard.h \ + src/vs/JTApplication.h } !noupdater:!linux-g++:!linux-g++-64 { HEADERS += src/dblsqd/feed.h \ @@ -325,21 +325,22 @@ SOURCES += src/DataProtocol.cpp \ src/gui/qjacktrip.cpp \ src/gui/about.cpp \ src/gui/textbuf.cpp \ - src/gui/vuMeter.cpp + src/gui/vuMeter.cpp \ + src/UserInterface.cpp !novs { - SOURCES += src/gui/virtualstudio.cpp \ - src/gui/vsApi.cpp \ - src/gui/vsAuth.cpp \ - src/gui/vsDeviceCodeFlow.cpp \ - src/gui/vsDeeplink.cpp \ - src/gui/vsDevice.cpp \ - src/gui/vsAudio.cpp \ - src/gui/vsServerInfo.cpp \ - src/gui/vsQuickView.cpp \ - src/gui/vsWebSocket.cpp \ - src/gui/vsPermissions.cpp \ - src/gui/vsPinger.cpp \ - src/gui/vsPing.cpp + SOURCES += src/vs/virtualstudio.cpp \ + src/vs/vsApi.cpp \ + src/vs/vsAuth.cpp \ + src/vs/vsDeviceCodeFlow.cpp \ + src/vs/vsDeeplink.cpp \ + src/vs/vsDevice.cpp \ + src/vs/vsAudio.cpp \ + src/vs/vsServerInfo.cpp \ + src/vs/vsQuickView.cpp \ + src/vs/vsWebSocket.cpp \ + src/vs/vsPermissions.cpp \ + src/vs/vsPinger.cpp \ + src/vs/vsPing.cpp } !noupdater:!linux-g++:!linux-g++-64 { SOURCES += src/dblsqd/feed.cpp \ @@ -351,20 +352,19 @@ SOURCES += src/DataProtocol.cpp \ !nogui { macx { - HEADERS += src/gui/NoNap.h - OBJECTIVE_SOURCES += src/gui/NoNap.mm + HEADERS += src/NoNap.h + OBJECTIVE_SOURCES += src/NoNap.mm !novs { - HEADERS += src/gui/vsMacPermissions.h - OBJECTIVE_SOURCES += src/gui/vsMacPermissions.mm + HEADERS += src/vs/vsMacPermissions.h + OBJECTIVE_SOURCES += src/vs/vsMacPermissions.mm } } FORMS += src/gui/qjacktrip.ui \ src/gui/about.ui \ src/gui/messageDialog.ui - novs { - RESOURCES += src/gui/qjacktrip_novs.qrc - } else { - RESOURCES += src/gui/qjacktrip.qrc + RESOURCES += src/images/images.qrc + !novs { + RESOURCES += src/vs/vs.qrc } !noupdater:!linux-g++:!linux-g++-64 { FORMS += src/dblsqd/update_dialog.ui diff --git a/macos/assemble_app.sh b/macos/assemble_app.sh index 37c3d4c..22475f3 100755 --- a/macos/assemble_app.sh +++ b/macos/assemble_app.sh @@ -155,7 +155,7 @@ if [ -n "$DYNAMIC_QT" ]; then fi DEPLOY_OPTS="-executable=$APPNAME.app/Contents/MacOS/jacktrip -libpath=$QT_PATH/lib" if [ -n "$DYNAMIC_VS" ]; then - DEPLOY_OPTS="$DEPLOY_OPTS -qmldir=../src/gui" + DEPLOY_OPTS="$DEPLOY_OPTS -qmldir=../src/vs" fi $DEPLOY_CMD "$APPNAME.app" $DEPLOY_OPTS diff --git a/meson.build b/meson.build index 74ea661..0685415 100644 --- a/meson.build +++ b/meson.build @@ -128,88 +128,98 @@ else endif endif -if get_option('nogui') == true +if qt_version == '5' + deps += dependency('qt5', modules: ['Core', 'Network'], include_type: 'system') +else + deps += dependency('qt6', modules: ['Core', 'Network'], include_type: 'system') +endif + +if get_option('nogui') == true or (get_option('noclassic') == true and get_option('novs') == true) + # command line only defines += '-DNO_GUI' +else + # include vs and/or classic gui if qt_version == '5' - deps += dependency('qt5', modules: ['Core', 'Network'], include_type: 'system') + deps += dependency('qt5', modules: ['Gui', 'Widgets'], include_type: 'system') else - deps += dependency('qt6', modules: ['Core', 'Network'], include_type: 'system') + deps += dependency('qt6', modules: ['Gui', 'Widgets'], include_type: 'system') + endif + qres = ['src/images/images.qrc'] + src += 'src/UserInterface.cpp' + + if get_option('noclassic') == true + defines += '-DNO_CLASSIC' + else + # support classic mode + src += [ + 'src/gui/qjacktrip.cpp', + 'src/gui/about.cpp', + 'src/gui/messageDialog.cpp', + 'src/gui/textbuf.cpp', + 'src/gui/vuMeter.cpp' + ] + moc_h += [ + 'src/gui/about.h', + 'src/gui/qjacktrip.h', + 'src/gui/messageDialog.h', + 'src/gui/textbuf.h', + 'src/gui/vuMeter.h' + ] + ui_h += [ + 'src/gui/qjacktrip.ui', + 'src/gui/messageDialog.ui', + 'src/gui/about.ui' + ] endif -else - src += [ - 'src/gui/qjacktrip.cpp', - 'src/gui/about.cpp', - 'src/gui/messageDialog.cpp', - 'src/gui/textbuf.cpp', - 'src/gui/vuMeter.cpp' - ] - moc_h += [ - 'src/gui/about.h', - 'src/gui/qjacktrip.h', - 'src/gui/messageDialog.h', - 'src/gui/textbuf.h', - 'src/gui/vuMeter.h' - ] - ui_h += [ - 'src/gui/qjacktrip.ui', - 'src/gui/messageDialog.ui', - 'src/gui/about.ui' - ] if get_option('novs') == true defines += '-DNO_VS' - if qt_version == '5' - deps += dependency('qt5', modules: ['Core', 'Gui', 'Network', 'Widgets'], include_type: 'system') - else - deps += dependency('qt6', modules: ['Core', 'Gui', 'Network', 'Widgets'], include_type: 'system') - endif - qres = ['src/gui/qjacktrip_novs.qrc'] else src += [ - 'src/gui/virtualstudio.cpp', - 'src/gui/vsAuth.cpp', - 'src/gui/vsApi.cpp', - 'src/gui/vsDeviceCodeFlow.cpp', - 'src/gui/vsDeeplink.cpp', - 'src/gui/vsDevice.cpp', - 'src/gui/vsAudio.cpp', - 'src/gui/vsServerInfo.cpp', - 'src/gui/vsQuickView.cpp', - 'src/gui/vsWebSocket.cpp', - 'src/gui/vsPermissions.cpp', - 'src/gui/vsPinger.cpp', - 'src/gui/vsPing.cpp', - 'src/gui/WebSocketTransport.cpp' + 'src/vs/virtualstudio.cpp', + 'src/vs/vsAuth.cpp', + 'src/vs/vsApi.cpp', + 'src/vs/vsDeviceCodeFlow.cpp', + 'src/vs/vsDeeplink.cpp', + 'src/vs/vsDevice.cpp', + 'src/vs/vsAudio.cpp', + 'src/vs/vsServerInfo.cpp', + 'src/vs/vsQuickView.cpp', + 'src/vs/vsWebSocket.cpp', + 'src/vs/vsPermissions.cpp', + 'src/vs/vsPinger.cpp', + 'src/vs/vsPing.cpp', + 'src/vs/WebSocketTransport.cpp' ] moc_h += [ - 'src/gui/virtualstudio.h', - 'src/gui/vsApi.h', - 'src/gui/vsAuth.h', - 'src/gui/vsDeviceCodeFlow.h', - 'src/gui/vsDeeplink.h', - 'src/gui/vsDevice.h', - 'src/gui/vsAudio.h', - 'src/gui/vsServerInfo.h', - 'src/gui/vsQuickView.h', - 'src/gui/vsWebSocket.h', - 'src/gui/vsPermissions.h', - 'src/gui/vsPinger.h', - 'src/gui/vsPing.h', - 'src/gui/vsQmlClipboard.h', - 'src/JTApplication.h', - 'src/gui/WebSocketTransport.h' + 'src/vs/virtualstudio.h', + 'src/vs/vsApi.h', + 'src/vs/vsAuth.h', + 'src/vs/vsDeviceCodeFlow.h', + 'src/vs/vsDeeplink.h', + 'src/vs/vsDevice.h', + 'src/vs/vsAudio.h', + 'src/vs/vsServerInfo.h', + 'src/vs/vsQuickView.h', + 'src/vs/vsWebSocket.h', + 'src/vs/vsPermissions.h', + 'src/vs/vsPinger.h', + 'src/vs/vsPing.h', + 'src/vs/vsQmlClipboard.h', + 'src/vs/JTApplication.h', + 'src/vs/WebSocketTransport.h' ] if host_machine.system() == 'darwin' - moc_h += ['src/gui/vsMacPermissions.h'] + moc_h += ['src/vs/vsMacPermissions.h'] endif - if get_option('vsftux') == true + if get_option('vsftux') == true or get_option('noclassic') == true defines += '-DVS_FTUX' endif - deps += dependency('qt6', modules: ['Core', 'Gui', 'Network', 'Widgets', 'Core5Compat', 'Quick', 'QuickControls2', 'Qml', 'ShaderTools', 'Svg', 'WebSockets', 'WebEngineCore', 'WebEngineQuick', 'WebChannel'], include_type: 'system') - qres = ['src/gui/qjacktrip.qrc'] + deps += dependency('qt6', modules: ['Core5Compat', 'Quick', 'QuickControls2', 'Qml', 'ShaderTools', 'Svg', 'WebSockets', 'WebEngineCore', 'WebEngineQuick', 'WebChannel'], include_type: 'system') + qres += ['src/vs/vs.qrc'] endif if get_option('noupdater') == true or host_machine.system() == 'linux' @@ -223,8 +233,6 @@ else ] 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'] @@ -286,9 +294,10 @@ if get_option('default_library') == 'static' endif endif -# TODO: QT_OPENSOURCE should only be defined for open source Qt distribution -# in QMake this can be checked with QT_EDITION == 'OpenSource' -defines += '-DQT_OPENSOURCE' +# QT_OPENSOURCE should only be defined for open source Qt distribution +if get_option('qtedition') != 'commercial' + defines += '-DQT_OPENSOURCE' +endif rtaudio_dep = dependency('rtaudio', required: get_option('rtaudio')) if rtaudio_dep.found() == true @@ -305,7 +314,7 @@ if rtaudio_dep.found() == false and jack_dep.found() == false endif if host_machine.system() == 'darwin' - src += ['src/gui/NoNap.mm'] + src += ['src/NoNap.mm'] # Adding CoreAudio here is a workaround and should be removed # when https://github.com/thestk/rtaudio/issues/302 is fixed # and arrived in all common package managers @@ -315,8 +324,8 @@ if host_machine.system() == 'darwin' add_languages('objcpp') endif -if host_machine.system() == 'darwin' and get_option('novs') == false - src += ['src/gui/vsMacPermissions.mm'] +if host_machine.system() == 'darwin' and get_option('novs') == false and get_option('nogui') == false + src += ['src/vs/vsMacPermissions.mm'] apple_av_dep = dependency('appleframeworks', modules : ['avfoundation', 'webkit']) deps += apple_av_dep endif diff --git a/meson_options.txt b/meson_options.txt index e7daaf0..ec66535 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -4,9 +4,11 @@ option('jack', type : 'feature', value : 'auto', description: 'Build with JACK B 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('noclassic', type : 'boolean', value : false, description: 'Build without classic mode support') option('vsftux', type : 'boolean', value : false, description: 'Build with Virtual Studio first launch experience') option('noupdater', type : 'boolean', value : false, description: 'Build without auto-update support') option('nofeedback', type : 'boolean', value : false, description: 'Build without feedback detection') option('profile', type: 'combo', choices: ['default', 'development'], value: 'default', description: 'Choose build profile / Sets desktop id accordingly') option('qtversion', type : 'combo', choices: ['', '5', '6'], description: 'Choose to build with either Qt5 or Qt6') +option('qtedition', type : 'combo', choices: ['opensource', 'commercial'], description: 'Choose license edition for Qt') option('buildinfo', type : 'string', value : '', yield : true, description: 'Additional info used to describe the build') \ No newline at end of file diff --git a/src/Analyzer.cpp b/src/Analyzer.cpp index a87dff3..824eba6 100644 --- a/src/Analyzer.cpp +++ b/src/Analyzer.cpp @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/Analyzer.h b/src/Analyzer.h index 8c4e6fe..ac698bf 100644 --- a/src/Analyzer.h +++ b/src/Analyzer.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/AudioInterface.cpp b/src/AudioInterface.cpp index 918f696..97cb28d 100644 --- a/src/AudioInterface.cpp +++ b/src/AudioInterface.cpp @@ -819,7 +819,7 @@ void AudioInterface::setDevicesWarningMsg(warningMessageT msg) mWarningMsg = "The buffer size setting for your audio device will cause high latency " "or audio delay. Use an audio device that supports small buffer sizes " - "to reduce audio delays. Click for more info."; + "to reduce audio delays."; mWarningHelpUrl = "https://support.jacktrip.com/recommended-audio-interfaces"; mHighLatencyFlag = true; break; @@ -827,7 +827,7 @@ void AudioInterface::setDevicesWarningMsg(warningMessageT msg) mWarningMsg = "You audio device drivers may cause high latency or audio delay. Install " "and use ASIO drivers provided by your device's manufacturer to reduce " - "audio delays. Click for more info."; + "audio delays."; mWarningHelpUrl = "https://support.jacktrip.com/troubleshooting-windows-drivers-and-asio"; mHighLatencyFlag = true; @@ -839,6 +839,13 @@ void AudioInterface::setDevicesWarningMsg(warningMessageT msg) mWarningHelpUrl = ""; mHighLatencyFlag = true; break; + case DEVICE_WARN_SPEAKERS: + mWarningMsg = + "You appear to have selected speakers for audio output. " + "Using speakers with microphones will likely cause a loud feedback " + "loop. We strongly recommend that you use wired headphones instead."; + mWarningHelpUrl = "https://support.jacktrip.com/recommended-audio-interfaces"; + break; default: mWarningMsg = ""; mWarningHelpUrl = ""; diff --git a/src/AudioInterface.h b/src/AudioInterface.h index 6ed62be..d752222 100644 --- a/src/AudioInterface.h +++ b/src/AudioInterface.h @@ -84,7 +84,8 @@ class AudioInterface DEVICE_WARN_NONE, DEVICE_WARN_BUFFER_LATENCY, DEVICE_WARN_ASIO_LATENCY, - DEVICE_WARN_ALSA_LATENCY + DEVICE_WARN_ALSA_LATENCY, + DEVICE_WARN_SPEAKERS }; enum errorMessageT { diff --git a/src/Meter.cpp b/src/Meter.cpp index 9130f15..a641e8b 100644 --- a/src/Meter.cpp +++ b/src/Meter.cpp @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/Meter.h b/src/Meter.h index 7fac7ea..05b2fbf 100644 --- a/src/Meter.h +++ b/src/Meter.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/Monitor.cpp b/src/Monitor.cpp index fb17e16..2f9a7d2 100644 --- a/src/Monitor.cpp +++ b/src/Monitor.cpp @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/Monitor.h b/src/Monitor.h index 3a41763..6169b9d 100644 --- a/src/Monitor.h +++ b/src/Monitor.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/NoNap.h b/src/NoNap.h new file mode 100644 index 0000000..0eb69e8 --- /dev/null +++ b/src/NoNap.h @@ -0,0 +1,50 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2020 Aaron Wyatt. + + 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. +*/ +//***************************************************************** + +#ifndef __NONAP_H__ +#define __NONAP_H__ + +#include + +class NoNap +{ + public: + NoNap(); + ~NoNap(); + + void disableNap(); + void enableNap(); + + private: + id m_activity; + bool m_preventNap; +}; + +#endif // __NONAP_H__ diff --git a/src/NoNap.mm b/src/NoNap.mm new file mode 100644 index 0000000..5f0c48a --- /dev/null +++ b/src/NoNap.mm @@ -0,0 +1,63 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2020 Aaron Wyatt. + + 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. +*/ +//***************************************************************** + +#include "NoNap.h" +#include + +NoNap::NoNap() : + m_preventNap(false) +{} + +void NoNap::disableNap() +{ + if (m_preventNap) { + return; + } + m_preventNap = true; + m_activity = [[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityLatencyCritical | NSActivityUserInitiated reason:@"Disable App Nap"]; + [m_activity retain]; +} + +void NoNap::enableNap() +{ + if (!m_preventNap) { + return; + } + m_preventNap = false; + [[NSProcessInfo processInfo] endActivity:m_activity]; + [m_activity release]; +} + +NoNap::~NoNap() +{ + if (m_preventNap) { + enableNap(); + } +} diff --git a/src/Patcher.cpp b/src/Patcher.cpp index f4aa065..1a4f796 100644 --- a/src/Patcher.cpp +++ b/src/Patcher.cpp @@ -60,6 +60,13 @@ void Patcher::registerClient(const QString& clientName) { QMutexLocker locker(&m_connectionMutex); + // this works around a JACK timing bug found under pipewire + // if registerClient is called for a second (or subsequent) hub client + // jack_client won't have properly updated its ports + // the workaround is to sleep here and let JACK update + if (m_jackClient) + QThread::msleep(100); + // If our jack client isn't running, start it. if (!m_jackClient) { m_jackClient = jack_client_open("jthubpatcher", JackNoStartServer, &m_status); diff --git a/src/Regulator.cpp b/src/Regulator.cpp index d73bc96..f847a0d 100644 --- a/src/Regulator.cpp +++ b/src/Regulator.cpp @@ -104,7 +104,7 @@ constexpr double AutoInitValFactor = // tweak constexpr int WindowDivisor = 8; // for faster auto tracking constexpr double AutoHeadroomGlitchTolerance = - 0.02; // Acceptable rate of skips before auto headroom is increased (2.0%) + 0.01; // Acceptable rate of skips before auto headroom is increased (1.0%) constexpr double AutoHistoryWindow = 60; // rolling window of time (in seconds) over which auto tolerance roughly adjusts constexpr double AutoSmoothingFactor = @@ -477,7 +477,7 @@ void Regulator::updateTolerance(int glitches, int skipped) if (mAutoHeadroom < 0) { // variable headroom: automatically increase to minimize glitch counts // only increase headroom if doing so would have reduced the number of - // glitches that occured over the past two seconds by 2% or more. + // glitches that occured over the past second by 1% or more. // prevent headroom from growing beyond rolling average of max. const int skipsAllowed = static_cast(AutoHeadroomGlitchTolerance * mSampleRate / mPeerFPP); @@ -486,6 +486,7 @@ void Regulator::updateTolerance(int glitches, int skipped) if (mSkipAutoHeadroom) { mSkipAutoHeadroom = false; } else { + // don't increase headroom two intervals in a row mSkipAutoHeadroom = true; ++mCurrentHeadroom; cout << "PLC glitches=" << glitches << " skipped=" << skipped << ">" @@ -493,7 +494,8 @@ void Regulator::updateTolerance(int glitches, int skipped) << " (max=" << pushStat->longTermMax << ")" << endl; } } else { - mSkipAutoHeadroom = true; + // require 2 seconds in a row if headroom >= two packet intervals + mSkipAutoHeadroom = mMsecTolerance >= (mPeerFPPdurMsec * 2); } } else { // fixed headroom @@ -591,7 +593,8 @@ bool Regulator::pullPacket() if (skipped < 0) skipped += NumSlots; } - if (mIncomingTiming[next] + mMsecTolerance >= now) { + // check if packet's age matches tolerance, or is the best candidate we have + if (mIncomingTiming[next] + mMsecTolerance >= now || i == 0) { // next is the best candidate memcpy(mXfrBuffer, mSlots[next], mPeerBytes); mLastSeqNumOut = next; diff --git a/src/RtAudioInterface.cpp b/src/RtAudioInterface.cpp index fc17538..3fa35cd 100644 --- a/src/RtAudioInterface.cpp +++ b/src/RtAudioInterface.cpp @@ -258,6 +258,14 @@ void RtAudioInterface::setup(bool verbose) throw std::runtime_error(errorMsg.toStdString()); } + // provide warnings for common known failure cases + const QString out_device_lower_name = + QString::fromStdString(out_device.name).toLower(); + if (out_device_lower_name.contains("speakers") + || out_device_lower_name.contains("lautsprecher")) { + AudioInterface::setDevicesWarningMsg(AudioInterface::DEVICE_WARN_SPEAKERS); + } + if (in_device.api == out_device.api) { #ifdef _WIN32 if (in_device.api != RtAudio::WINDOWS_ASIO) { diff --git a/src/UdpDataProtocol.cpp b/src/UdpDataProtocol.cpp index 7c9eb37..9ae8709 100644 --- a/src/UdpDataProtocol.cpp +++ b/src/UdpDataProtocol.cpp @@ -50,6 +50,7 @@ #include "jacktrip_globals.h" #ifdef _WIN32 // #include +#include #include #include //cc need SD_SEND #pragma comment(lib, "ws2_32.lib") @@ -195,11 +196,7 @@ void UdpDataProtocol::setPeerAddress(const char* peerHostOrIP) } } -#if defined(_WIN32) -void UdpDataProtocol::setSocket(SOCKET& socket) -#else -void UdpDataProtocol::setSocket(int& socket) -#endif +void UdpDataProtocol::setSocket(socket_type& socket) { // If we haven't been passed a valid socket, then we should bind one. #if defined(_WIN32) @@ -221,11 +218,7 @@ void UdpDataProtocol::setSocket(int& socket) } //******************************************************************************* -#if defined(_WIN32) -SOCKET UdpDataProtocol::bindSocket() -#else -int UdpDataProtocol::bindSocket() -#endif +socket_type UdpDataProtocol::bindSocket() { QMutexLocker locker(&sUdpMutex); @@ -301,45 +294,104 @@ int UdpDataProtocol::bindSocket() ::setsockopt(sock_fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); #endif + // set qos for windows after flow is established (requires peer address/port) + if (setSocketQos(sock_fd)) { + std::cout << "Set QoS for network socket" << std::endl; + } else { + std::cerr << "Failed to set QoS for network socket" << std::endl; + } + + // Bind the Socket + if (mIPv6) { + if ((::bind(sock_fd, (struct sockaddr*)&local_addr6, sizeof(local_addr6))) < 0) { + throw std::runtime_error("ERROR: UDP Socket Bind Error"); + } + } else { + if ((::bind(sock_fd, (struct sockaddr*)&local_addr, sizeof(local_addr))) < 0) { + throw std::runtime_error("ERROR: UDP Socket Bind Error"); + } + } + + // Return our file descriptor so the socket can be shared for a + // full duplex connection. + return sock_fd; +} + +bool UdpDataProtocol::setSocketQos(socket_type& sock_fd) +{ #if defined(_WIN32) - // TODO: these don't seem to work on windows. we likely need to use qWAVE or qos2 + // Windows QoS (qWave) for audio traffic flows + // https://learn.microsoft.com/en-us/windows/win32/api/_qos/ + // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/qos/qwave-api-reference + + // Initialize the QoS version parameter. + QOS_VERSION Version; + Version.MajorVersion = 1; + Version.MinorVersion = 0; + + // Get a handle to the QoS subsystem. + HANDLE QoSHandle = NULL; + BOOL QoSResult = QOSCreateHandle(&Version, &QoSHandle); + if (QoSResult != TRUE) { + std::cerr << "QOSCreateHandle failed. Error: " << WSAGetLastError() << std::endl; + return false; + } + + // Add socket to flow. + QOS_FLOWID QoSFlowId = 0; // Flow Id must be 0. + PSOCKADDR pSockAddr; + if (mIPv6) { + pSockAddr = reinterpret_cast(&mPeerAddr6); + } else { + pSockAddr = reinterpret_cast(&mPeerAddr); + } + // Note: QOSTrafficTypeVoice sets DSCP to 56 (high VO for WMM) + // without having to call QOSSetFlow(). This is best for voice. + QoSResult = QOSAddSocketToFlow(QoSHandle, sock_fd, pSockAddr, QOSTrafficTypeVoice, + QOS_NON_ADAPTIVE_FLOW, &QoSFlowId); + if (QoSResult != TRUE) { + std::cerr << "QOSAddSocketToFlow failed. Error: "; + std::cerr << WSAGetLastError() << std::endl; + return false; + } #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)); + int result = + ::setsockopt(sock_fd, SOL_SOCKET, SO_NET_SERVICE_TYPE, &val, sizeof(val)); + if (result != 0) { + std::cerr << "setsockopt failed. Error: " << errno << std::endl; + return false; + } #else - // Set ToS to DSCP Expedited Forwarding (EF), recommended for Audio + // Set ToS to DSCP 56 (high VO for WMM), 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 + const char tos = 0xE0; // 11100000 (56 << 2) + int result; if (mIPv6) { - ::setsockopt(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)); + result = ::setsockopt(sock_fd, IPPROTO_IPV6, IPV6_TCLASS, &tos, sizeof(tos)); } else { - ::setsockopt(sock_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + result = ::setsockopt(sock_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + } + if (result != 0) { + std::cerr << "setsockopt failed. Error: " << errno << std::endl; + return false; } // 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) { - throw std::runtime_error("ERROR: UDP Socket Bind Error"); - } - } else { - if ((::bind(sock_fd, (struct sockaddr*)&local_addr, sizeof(local_addr))) < 0) { - throw std::runtime_error("ERROR: UDP Socket Bind Error"); - } + result = ::setsockopt(sock_fd, SOL_SOCKET, SO_PRIORITY, &priority, sizeof(priority)); + if (result != 0) { + std::cerr << "setsockopt failed. Error: " << errno << std::endl; + return false; } +#endif - // Return our file descriptor so the socket can be shared for a - // full duplex connection. - return sock_fd; + return true; } void UdpDataProtocol::processControlPacket(const char* buf) diff --git a/src/UdpDataProtocol.h b/src/UdpDataProtocol.h index f49ffae..498c2be 100644 --- a/src/UdpDataProtocol.h +++ b/src/UdpDataProtocol.h @@ -50,6 +50,12 @@ #include "jacktrip_globals.h" #include "jacktrip_types.h" +#if defined(_WIN32) +typedef SOCKET socket_type; +#else +typedef int socket_type; +#endif + /** \brief UDP implementation of DataProtocol class * * The class has a bind port and a peer port. The meaning of these @@ -93,11 +99,7 @@ class UdpDataProtocol : public DataProtocol */ void setPeerAddress(const char* peerHostOrIP); -#if defined(_WIN32) - void setSocket(SOCKET& socket); -#else - void setSocket(int& socket); -#endif + void setSocket(socket_type& socket); void processControlPacket(const char* buf); @@ -168,11 +170,11 @@ class UdpDataProtocol : public DataProtocol protected: /** \brief Binds the UDP socket to the available address and specified port */ -#if defined(_WIN32) - SOCKET bindSocket(); -#else - int bindSocket(); -#endif + socket_type bindSocket(); + + /** \brief Setup QoS for the network socket/flow + */ + bool setSocketQos(socket_type& sock_fd); /** \brief This function blocks until data is available for reading in the * socket. The function will timeout after timeout_msec microseconds. diff --git a/src/UserInterface.cpp b/src/UserInterface.cpp new file mode 100644 index 0000000..e2a6ca2 --- /dev/null +++ b/src/UserInterface.cpp @@ -0,0 +1,294 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024 Michael Dickey, Aaron Wyatt. + + 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 UserInterface.cpp + * \author Michael Dickey, Aaron Wyatt + * \date August 2024 + */ + +#include "UserInterface.h" + +#include + +#ifndef NO_VS +#include "vs/virtualstudio.h" +#endif // NO_VS + +#ifndef NO_CLASSIC +#include "gui/qjacktrip.h" +#endif // NO_CLASSIC + +#if !defined(NO_UPDATER) && !defined(__unix__) +#include "dblsqd/feed.h" +#include "dblsqd/update_dialog.h" +#endif // !defined(NO_UPDATER) && !defined(__unix__) + +#ifdef _WIN32 +#include +#include +#include + +bool isRunFromCmd() +{ + // Get our parent process pid + HANDLE h = NULL; + PROCESSENTRY32 pe; + ZeroMemory(&pe, sizeof(PROCESSENTRY32)); + DWORD pid = GetCurrentProcessId(); + DWORD ppid = 0; + pe.dwSize = sizeof(PROCESSENTRY32); + h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (Process32First(h, &pe)) { + do { + // Loop through the list of processes until we find ours. + if (pe.th32ProcessID == pid) { + ppid = pe.th32ParentProcessID; + break; + } + } while (Process32Next(h, &pe)); + } + CloseHandle(h); + + // Get the name of our parent process; + char pname[MAX_PATH] = {0}; + DWORD size = MAX_PATH; + h = NULL; + h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, ppid); + if (h) { + if (QueryFullProcessImageNameA(h, 0, pname, &size)) { + CloseHandle(h); + + // Check if our parent process is a command line. + if (size >= 14 && strncmp(pname + size - 14, "powershell.exe", 14) == 0) { + return true; + } + 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; + } + // a few extras for msys/cygwin/etc + if (size >= 8 && strncmp(pname + size - 8, "bash.exe", 8) == 0) { + return true; + } + if (size >= 6 && strncmp(pname + size - 6, "sh.exe", 6) == 0) { + return true; + } + if (size >= 7 && strncmp(pname + size - 7, "zsh.exe", 7) == 0) { + return true; + } + } else { + CloseHandle(h); + } + } + + return false; +} +#endif // _WIN32 + +UserInterface::UserInterface(QSharedPointer& settings) : m_cliSettings(settings) +{ +} + +UserInterface::~UserInterface() +{ +#ifndef NO_VS + m_vs_ui.clear(); +#endif +#ifndef NO_CLASSIC + m_classic_ui.clear(); +#endif +} + +QCoreApplication* UserInterface::createApplication(int& argc, char* argv[]) +{ +#if defined(__unix__) + // Check if X or Wayland environment variables are set. + if (std::getenv("WAYLAND_DISPLAY") == nullptr && std::getenv("DISPLAY") == nullptr) { + std::cout << "ERROR: Display not found. Make sure X or Wayland is running or " + "try running jacktrip in command line mode." + << std::endl; + std::cout << "(To display a list of command line options run \"jacktrip -h\")" + << std::endl; + std::exit(1); + } +#endif + + QCoreApplication* app; +#ifdef NO_VS + app = QJackTrip::createApplication(argc, argv); +#else + app = VirtualStudio::createApplication(argc, argv); +#endif + app->setOrganizationName(QStringLiteral("jacktrip")); + app->setOrganizationDomain(QStringLiteral("jacktrip.org")); + app->setApplicationName(QStringLiteral("JackTrip")); + app->setApplicationVersion(gVersion); + + return app; +} + +void UserInterface::start(QApplication* app) +{ +#ifdef _WIN32 + // Remove the console that appears if we're on windows and not running from a + // command line. + if (!isRunFromCmd()) { + std::cout << "This extra window is caused by a bug in Microsoft Windows. " + << "It can safely be ignored or closed." << std::endl + << std::endl + << "To fix this bug, please upgrade to the latest version of " + << "Windows Terminal available in the Microsoft App Store:" << std::endl + << "https://aka.ms/terminal" << std::endl; + + FreeConsole(); + } +#endif // _WIN32 + +#ifndef NO_CLASSIC + m_classic_ui.reset(new QJackTrip(*this)); + QObject::connect(m_classic_ui.data(), &QJackTrip::signalExit, app, + &QCoreApplication::quit, Qt::QueuedConnection); +#ifdef NO_VS + m_classic_ui->show(); +#endif // NO_VS +#endif // NO_CLASSIC + + QSettings settings; + +#ifndef NO_VS + m_vs_ui.reset(new VirtualStudio(*this)); + QObject::connect(m_vs_ui.data(), &VirtualStudio::signalExit, app, + &QCoreApplication::quit, Qt::QueuedConnection); + // Check which mode we are running in + uiModeT uiMode = UserInterface::MODE_UNSET; + if (!m_cliSettings->getDeeplink().isEmpty()) { + uiMode = MODE_VS; + } else if (m_cliSettings->guiForceClassicMode()) { + uiMode = MODE_CLASSIC; + // force settings change; otherwise, virtual studio + // window will still be displayed + settings.setValue(QStringLiteral("UiMode"), uiMode); + } else { + uiMode = static_cast( + settings.value(QStringLiteral("UiMode"), MODE_UNSET).toInt()); + } + setMode(uiMode); +#endif // NO_VS + +#if !defined(NO_UPDATER) && !defined(__unix__) +#ifndef PSI + QString updateChannel = + settings.value(QStringLiteral("UpdateChannel"), "stable").toString().toLower(); + QString baseUrl = + QStringLiteral("https://files.jacktrip.org/app-releases/%1").arg(updateChannel); +#else + QString baseUrl = QStringLiteral("https://nuages.psi-borg.org/jacktrip"); +#endif // PSI + // Setup auto-update feed + dblsqd::Feed* feed = new dblsqd::Feed(); +#ifdef _WIN32 + feed->setUrl(QUrl(QString("%1/%2-manifests.json").arg(baseUrl, "win"))); +#endif +#ifdef Q_OS_MACOS + feed->setUrl(QUrl(QString("%1/%2-manifests.json").arg(baseUrl, "mac"))); +#endif + if (feed) { + dblsqd::UpdateDialog* updateDialog = new dblsqd::UpdateDialog(feed); + updateDialog->setIcon(":/qjacktrip/icon.png"); + } +#endif // !defined(NO_UPDATER) && !defined(__unix__) +} + +void UserInterface::setMode(uiModeT m) +{ +#ifdef NO_VS + if (m == MODE_VS) { + std::cerr << "JackTrip was not built with support for Virtual Studio mode." + << std::endl; + } + m = MODE_CLASSIC; +#endif + +#ifdef NO_CLASSIC + if (m == MODE_CLASSIC) { + std::cerr << "JackTrip was not built with support for Classic mode." << std::endl; + } + m = MODE_VS; +#endif + + switch (m) { + case MODE_UNSET: + case MODE_VS: +#ifndef NO_VS + m_vs_ui->show(); + if (m == MODE_VS || (m == MODE_UNSET && m_vs_ui->vsFtux())) { + m_vs_ui->setWindowState(QStringLiteral("login")); + } else if (m == MODE_UNSET && !m_vs_ui->vsFtux()) { + m_vs_ui->setWindowState(QStringLiteral("start")); + } + if (m_vs_ui->windowState() == "login") + m_vs_ui->login(); +#ifndef NO_CLASSIC + if (m_uiMode == MODE_CLASSIC) + m_classic_ui->hide(); +#endif // NO_CLASSIC +#endif // NO_VS + m_uiMode = MODE_VS; + break; + case MODE_CLASSIC: +#ifndef NO_CLASSIC + m_classic_ui->show(); +#ifndef NO_VS + m_vs_ui->hide(); +#endif // NO_VS +#endif // NO_CLASSIC + m_uiMode = MODE_CLASSIC; + break; + default: + return; + } +} + +void UserInterface::enableNap() +{ +#ifdef __APPLE__ + m_noNap.enableNap(); +#endif +} + +void UserInterface::disableNap() +{ +#ifdef __APPLE__ + m_noNap.disableNap(); +#endif +} diff --git a/src/UserInterface.h b/src/UserInterface.h new file mode 100644 index 0000000..7fd9e49 --- /dev/null +++ b/src/UserInterface.h @@ -0,0 +1,111 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2024 Michael Dickey, Aaron Wyatt. + + 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 UserInterface.h + * \author Michael Dickey + * \date August 2024 + */ + +#ifndef __USERINTERFACE_H__ +#define __USERINTERFACE_H__ + +#include +#include +#include + +#include "Settings.h" + +#ifdef __APPLE__ +#include "NoNap.h" +#endif + +// forward class declarations +class VirtualStudio; +class QJackTrip; + +/// UserInterface manages graphical user interfaces for JackTrip +class UserInterface +{ + public: + /** + * @brief which GUI mode is in use + * + * MODE_UNSET: none selected yet + * MODE_VS: Virtual Studio mode (QML/Quick interface) + * MODE_CLASSIC: Classic mode (QJackTrip interface) + */ + enum uiModeT { MODE_UNSET, MODE_VS, MODE_CLASSIC }; + + /// construction requires command line settings + explicit UserInterface(QSharedPointer& settings); + + /// @brief simple destructor + ~UserInterface(); + + /// @return current GUI mode + inline uiModeT getMode() const { return m_uiMode; } + + /// @return command line settings + inline Settings& getSettings() { return *m_cliSettings.data(); } + + /// @brief creates new application using command line arguments + static QCoreApplication* createApplication(int& argc, char* argv[]); + + /// @brief starts graphical user interface + void start(QApplication* app); + + /// @brief sets GUI mode + void setMode(uiModeT m); + + /// @brief enables napping for OSX + void enableNap(); + + /// @brief disables napping for OSX + void disableNap(); + + private: + uiModeT m_uiMode = MODE_UNSET; + QSharedPointer m_cliSettings; + +#ifndef NO_VS + QSharedPointer m_vs_ui; +#endif + +#ifndef NO_CLASSIC + QSharedPointer m_classic_ui; +#endif + +#ifdef __APPLE__ + NoNap m_noNap; +#endif +}; + +#endif diff --git a/src/Volume.cpp b/src/Volume.cpp index f15f9aa..1690b07 100644 --- a/src/Volume.cpp +++ b/src/Volume.cpp @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/Volume.h b/src/Volume.h index 8e8e12f..c4fa440 100644 --- a/src/Volume.h +++ b/src/Volume.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2020 Julius Smith, Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/WaitFreeFrameBuffer.h b/src/WaitFreeFrameBuffer.h index 41887fa..740fb6b 100644 --- a/src/WaitFreeFrameBuffer.h +++ b/src/WaitFreeFrameBuffer.h @@ -3,9 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2023 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. - JackTrip Labs, Inc. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/WaitFreeRingBuffer.h b/src/WaitFreeRingBuffer.h index 1b415d4..83ac360 100644 --- a/src/WaitFreeRingBuffer.h +++ b/src/WaitFreeRingBuffer.h @@ -3,9 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2023 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. - JackTrip Labs, Inc. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/NoNap.h b/src/gui/NoNap.h deleted file mode 100644 index 0fe3eda..0000000 --- a/src/gui/NoNap.h +++ /dev/null @@ -1,45 +0,0 @@ -//***************************************************************** -/* - QJackTrip: Bringing a graphical user interface to JackTrip, a - system for high quality audio network performance over the - internet. - - Copyright (c) 2020 Aaron Wyatt. - - This file is part of QJackTrip. - - QJackTrip is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - QJackTrip is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with QJackTrip. If not, see . -*/ -//***************************************************************** - -#ifndef __NONAP_H__ -#define __NONAP_H__ - -#include - -class NoNap -{ - public: - NoNap(); - ~NoNap(); - - void disableNap(); - void enableNap(); - - private: - id m_activity; - bool m_preventNap; -}; - -#endif // __NONAP_H__ diff --git a/src/gui/NoNap.mm b/src/gui/NoNap.mm deleted file mode 100644 index cb2edb2..0000000 --- a/src/gui/NoNap.mm +++ /dev/null @@ -1,58 +0,0 @@ -//***************************************************************** -/* - QJackTrip: Bringing a graphical user interface to JackTrip, a - system for high quality audio network performance over the - internet. - - Copyright (c) 2020 Aaron Wyatt. - - This file is part of QJackTrip. - - QJackTrip is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - QJackTrip is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with QJackTrip. If not, see . -*/ -//***************************************************************** - -#include "NoNap.h" -#include - -NoNap::NoNap() : - m_preventNap(false) -{} - -void NoNap::disableNap() -{ - if (m_preventNap) { - return; - } - m_preventNap = true; - m_activity = [[NSProcessInfo processInfo] beginActivityWithOptions:NSActivityLatencyCritical | NSActivityUserInitiated reason:@"Disable App Nap"]; - [m_activity retain]; -} - -void NoNap::enableNap() -{ - if (!m_preventNap) { - return; - } - m_preventNap = false; - [[NSProcessInfo processInfo] endActivity:m_activity]; - [m_activity release]; -} - -NoNap::~NoNap() -{ - if (m_preventNap) { - enableNap(); - } -} diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 5c25979..bfe7afe 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -87,7 +87,7 @@ About::About(QWidget* parent) : QDialog(parent), m_ui(new Ui::About) m_ui->aboutLabel->text().replace(QLatin1String("%BUILD%"), buildString)); #ifdef __APPLE__ - m_ui->aboutImage->setPixmap(QPixmap(":/qjacktrip/about@2x.png")); + m_ui->aboutImage->setPixmap(QPixmap(":/images/icon_256.png")); #endif aboutText.setHtml(m_ui->aboutLabel->text()); diff --git a/src/gui/about.ui b/src/gui/about.ui index e644f2e..e30f1a6 100644 --- a/src/gui/about.ui +++ b/src/gui/about.ui @@ -41,7 +41,7 @@ border: 4px solid black; - :/qjacktrip/about.png + :/images/icon_128.png true diff --git a/src/gui/qjacktrip.cpp b/src/gui/qjacktrip.cpp index 0cfbabe..f7a91ec 100644 --- a/src/gui/qjacktrip.cpp +++ b/src/gui/qjacktrip.cpp @@ -35,9 +35,6 @@ #include #include "about.h" -#ifndef NO_VS -#include "virtualstudio.h" -#endif #include "ui_qjacktrip.h" #ifdef USE_WEAK_JACK #include "weak_libjack.h" @@ -54,9 +51,9 @@ #include "../Meter.h" #include "../Reverb.h" -QJackTrip::QJackTrip(QSharedPointer settings, bool suppressCommandlineWarning, - QWidget* parent) +QJackTrip::QJackTrip(UserInterface& interface, QWidget* parent) : QMainWindow(parent) + , m_interface(interface) , m_ui(new Ui::QJackTrip) , m_netManager(new QNetworkAccessManager(this)) , m_statsDialog(new MessageDialog(this, QStringLiteral("Stats"))) @@ -66,11 +63,9 @@ QJackTrip::QJackTrip(QSharedPointer settings, bool suppressCommandline , m_jackTripRunning(false) , m_isExiting(false) , m_exitSent(false) - , m_suppressCommandlineWarning(suppressCommandlineWarning) , m_hideWarning(false) { m_ui->setupUi(this); - m_cliSettings = settings; // Set up our debug window, and relay everything to our real cout. std::cout.rdbuf(m_debugDialog->getOutputStream()->rdbuf()); @@ -109,9 +104,11 @@ QJackTrip::QJackTrip(QSharedPointer settings, bool suppressCommandline QStringLiteral("(This is for JackTrip's inbuilt authentication system. To easily " "connect to a Virtual Studio server, download a Virtual Studio " "enabled version of JackTrip.)")); + m_ui->vsModeButton->setVisible(false); #else connect(m_ui->vsModeButton, &QPushButton::clicked, this, &QJackTrip::virtualStudioMode); + m_ui->vsModeButton->setVisible(true); #endif connect(m_ui->autoPatchComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [=]() { @@ -249,7 +246,6 @@ QJackTrip::QJackTrip(QSharedPointer settings, bool suppressCommandline m_ui->autoPatchGroupBox->setVisible(false); m_ui->requireAuthGroupBox->setVisible(false); m_ui->backendWarningLabel->setVisible(false); - m_ui->vsModeButton->setVisible(false); m_ui->inputGroupBox->setVisible(false); m_ui->outputGroupBox->setVisible(false); @@ -378,10 +374,10 @@ void QJackTrip::showEvent(QShowEvent* event) QMainWindow::showEvent(event); if (m_firstShow) { QSettings settings; - loadSettings(m_cliSettings.data()); + loadSettings(&m_interface.getSettings()); // Display a warning about any ignored command line options. - if (m_cliSettings->guiIgnoresArguments() && !m_suppressCommandlineWarning) { + if (m_interface.getSettings().guiIgnoresArguments()) { QMessageBox msgBox; msgBox.setText( "You have supplied command line options that the GUI version of JackTrip " @@ -484,14 +480,6 @@ void QJackTrip::showEvent(QShowEvent* event) } } -#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) { @@ -499,9 +487,7 @@ void QJackTrip::processFinished() return; } m_jackTripRunning = false; -#ifdef __APPLE__ - m_noNap.enableNap(); -#endif + m_interface.enableNap(); m_ui->disconnectButton->setEnabled(false); if (m_ui->typeComboBox->currentIndex() == HUB_SERVER) { m_udpHub.reset(); @@ -1058,9 +1044,7 @@ void QJackTrip::start() m_ui->addressComboBox->insertItem(0, serverAddress); m_ui->addressComboBox->setCurrentIndex(0); -#ifdef __APPLE__ - m_noNap.disableNap(); -#endif + m_interface.disableNap(); } void QJackTrip::stop() @@ -1123,9 +1107,9 @@ void QJackTrip::updatedOutputMeasurements(const float* valuesInDb, int numChanne #ifndef NO_VS void QJackTrip::virtualStudioMode() { - this->hide(); - m_vs->show(); - m_vs->toVirtualStudio(); + m_interface.setMode(UserInterface::MODE_VS); + QSettings settings; + settings.setValue(QStringLiteral("UiMode"), UserInterface::MODE_VS); } #endif @@ -2044,3 +2028,24 @@ QJackTrip::~QJackTrip() std::cout.rdbuf(m_realCout.rdbuf()); std::cerr.rdbuf(m_realCerr.rdbuf()); } + +QCoreApplication* QJackTrip::createApplication(int& argc, char* argv[]) +{ + // Turn on high DPI support. +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif + + // Fix for display scaling like 125% or 150% on Windows +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) +#if defined(NO_VS) && defined(_WIN32) + QGuiApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); +#else + QGuiApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#endif // NO_VS +#endif // QT_VERSION + + return new QApplication(argc, argv); +} diff --git a/src/gui/qjacktrip.h b/src/gui/qjacktrip.h index 8b9a33f..0c61034 100644 --- a/src/gui/qjacktrip.h +++ b/src/gui/qjacktrip.h @@ -41,13 +41,10 @@ #include "../JackTrip.h" #include "../Settings.h" #include "../UdpHubListener.h" +#include "../UserInterface.h" #include "messageDialog.h" #include "vuMeter.h" -#ifdef __APPLE__ -#include "NoNap.h" -#endif - #ifdef RT_AUDIO #include #endif @@ -57,28 +54,18 @@ namespace Ui class QJackTrip; } // namespace Ui -#ifndef NO_VS -class VirtualStudio; -#endif - class QJackTrip : public QMainWindow { Q_OBJECT public: - explicit QJackTrip(QSharedPointer settings, - bool suppressCommandlineWarning = false, - QWidget* parent = nullptr); + explicit QJackTrip(UserInterface& interface, QWidget* parent = nullptr); ~QJackTrip() override; 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 + static QCoreApplication* createApplication(int& argc, char* argv[]); signals: void signalExit(); @@ -129,6 +116,7 @@ class QJackTrip : public QMainWindow JackTrip::hubConnectionModeT hubModeFromPatchType(patchTypeT patchType); + UserInterface& m_interface; QScopedPointer m_ui; QScopedPointer m_udpHub; QScopedPointer m_jackTrip; @@ -144,7 +132,6 @@ class QJackTrip : public QMainWindow bool m_isExiting; bool m_exitSent; - QSharedPointer m_cliSettings; bool m_suppressCommandlineWarning; float m_meterMax = 0.0; @@ -164,13 +151,6 @@ class QJackTrip : public QMainWindow QLabel m_autoQueueIndicator; bool m_hideWarning; bool m_firstShow = true; - -#ifndef NO_VS - QSharedPointer m_vs; -#endif -#ifdef __APPLE__ - NoNap m_noNap; -#endif }; #endif // QJACKTRIP_H diff --git a/src/gui/qjacktrip.ui b/src/gui/qjacktrip.ui index b6d0dd5..0c537cc 100644 --- a/src/gui/qjacktrip.ui +++ b/src/gui/qjacktrip.ui @@ -14,8 +14,8 @@ JackTrip - - :/qjacktrip/icon.png:/qjacktrip/icon.png + + :/images/icon_32.png:/images/icon_32.png diff --git a/src/gui/qjacktrip_novs.qrc b/src/gui/qjacktrip_novs.qrc deleted file mode 100644 index 179c85a..0000000 --- a/src/gui/qjacktrip_novs.qrc +++ /dev/null @@ -1,7 +0,0 @@ - - - about@2x.png - about.png - icon.png - - diff --git a/src/gui/vsQmlClipboard.h b/src/gui/vsQmlClipboard.h deleted file mode 100644 index 285b350..0000000 --- a/src/gui/vsQmlClipboard.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef VSQMLCLIPBOARD_H -#define VSQMLCLIPBOARD_H - -#include -#include -#include - -class VsQmlClipboard : public QObject -{ - Q_OBJECT - public: - explicit VsQmlClipboard(QObject* parent = 0) : QObject(parent) - { - clipboard = QApplication::clipboard(); - } - - Q_INVOKABLE void setText(QString text) - { - clipboard->setText(text, QClipboard::Clipboard); - } - - private: - QClipboard* clipboard; -}; - -#endif // VSQMLCLIPBOARD_H \ No newline at end of file diff --git a/src/gui/about.png b/src/images/icon_128.png similarity index 100% rename from src/gui/about.png rename to src/images/icon_128.png diff --git a/src/gui/about@2x.png b/src/images/icon_256.png similarity index 100% rename from src/gui/about@2x.png rename to src/images/icon_256.png diff --git a/src/gui/icon.png b/src/images/icon_32.png similarity index 100% rename from src/gui/icon.png rename to src/images/icon_32.png diff --git a/src/images/images.qrc b/src/images/images.qrc new file mode 100644 index 0000000..12f7129 --- /dev/null +++ b/src/images/images.qrc @@ -0,0 +1,7 @@ + + + icon_256.png + icon_128.png + icon_32.png + + diff --git a/src/jacktrip_globals.cpp b/src/jacktrip_globals.cpp index 6a31fbf..abd3017 100644 --- a/src/jacktrip_globals.cpp +++ b/src/jacktrip_globals.cpp @@ -152,11 +152,70 @@ void setRealtimeProcessPriority(int bufferSize, int sampleRate) #elif defined(_WIN32) void setRealtimeProcessPriority() { - if (SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == 0) { - std::cerr << "Failed to set process priority class." << std::endl; + if (SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == 0 + || GetPriorityClass(GetCurrentProcess()) != REALTIME_PRIORITY_CLASS) { + std::string priority = "unknown"; + switch (GetPriorityClass(GetCurrentProcess())) { + case ABOVE_NORMAL_PRIORITY_CLASS: + priority = "above normal"; + break; + case BELOW_NORMAL_PRIORITY_CLASS: + priority = "below normal"; + break; + case HIGH_PRIORITY_CLASS: + priority = "high"; + break; + case IDLE_PRIORITY_CLASS: + priority = "idle"; + break; + case NORMAL_PRIORITY_CLASS: + priority = "high"; + break; + case REALTIME_PRIORITY_CLASS: + priority = "realtime"; + break; + } + std::cerr << "Failed to set process priority class (priority = " << priority + << ")" << std::endl; + } else { + std::cout << "Set process priority class to realtime" << std::endl; } - if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == 0) { - std::cerr << "Failed to set thread priority." << std::endl; + if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == 0 + || GetThreadPriority(GetCurrentThread()) != THREAD_PRIORITY_TIME_CRITICAL) { + std::string priority = "unknown"; + switch (GetThreadPriority(GetCurrentThread())) { + case THREAD_MODE_BACKGROUND_BEGIN: + priority = "background begin"; + break; + case THREAD_MODE_BACKGROUND_END: + priority = "background end"; + break; + case THREAD_PRIORITY_ABOVE_NORMAL: + priority = "above normal"; + break; + case THREAD_PRIORITY_BELOW_NORMAL: + priority = "below normal"; + break; + case THREAD_PRIORITY_HIGHEST: + priority = "highest"; + break; + case THREAD_PRIORITY_IDLE: + priority = "idle"; + break; + case THREAD_PRIORITY_LOWEST: + priority = "lowest"; + break; + case THREAD_PRIORITY_NORMAL: + priority = "normal"; + break; + case THREAD_PRIORITY_TIME_CRITICAL: + priority = "time critical"; + break; + } + std::cerr << "Failed to set thread priority (priority = " << priority << ")" + << std::endl; + } else { + std::cout << "Set thread priority to time critical" << std::endl; } } #else diff --git a/src/jacktrip_globals.h b/src/jacktrip_globals.h index 0e587de..e61162a 100644 --- a/src/jacktrip_globals.h +++ b/src/jacktrip_globals.h @@ -40,7 +40,7 @@ #include "AudioInterface.h" -constexpr const char* const gVersion = "2.3.1"; ///< JackTrip version +constexpr const char* const gVersion = "2.4.0"; ///< JackTrip version //******************************************************************************* /// \name Default Values @@ -87,8 +87,7 @@ constexpr uint32_t gDefaultBufferSizeInSamples = 128; constexpr const char* gDefaultLocalAddress = ""; constexpr int gDefaultRedundancy = 1; constexpr int gTimeOutMultiThreadedServer = 10000; // seconds -constexpr int gWaitCounter = 60; -constexpr int gUdpWaitTimeout = 30; // milliseconds +constexpr int gUdpWaitTimeout = 50; // milliseconds //@} //******************************************************************************* diff --git a/src/main.cpp b/src/main.cpp index 58d91dd..8ef5908 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -35,41 +35,7 @@ * \date July 2020 */ -#ifndef NO_GUI -#include -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -#include -#endif - -#ifndef NO_UPDATER -#include "dblsqd/feed.h" -#include "dblsqd/update_dialog.h" -#endif - -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -#include -#include -#include -#include -#include -#include -#include -#include -#include -// TODO: Add support for QtWebView -// #include -#include - -#include "JTApplication.h" -#include "gui/virtualstudio.h" -#include "gui/vsDeeplink.h" -#include "gui/vsQmlClipboard.h" -#endif // NO_VS && QT_VERSION - -#include "gui/qjacktrip.h" -#else #include -#endif #include #include #include @@ -79,23 +45,21 @@ #include "UdpHubListener.h" #include "jacktrip_globals.h" +#ifndef NO_GUI +#include "UserInterface.h" +#endif + #ifdef _WIN32 #include #include #include #endif -#ifndef NO_GUI -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) -static QTextStream* ts; -static QFile outFile; -#endif // NO_VS && QT_VERSION -#endif // NO_GUI - QCoreApplication* createApplication(int& argc, char* argv[]) { // Check for some specific, GUI related command line options. bool forceGui = false; + bool testGui = false; for (int i = 1; i < argc; i++) { if (strncmp(argv[i], "--gui", 5) == 0 || strncmp(argv[i], "--deeplink", 10) == 0 || strncmp(argv[i], "--classic-gui", 13) == 0 @@ -105,115 +69,37 @@ QCoreApplication* createApplication(int& argc, char* argv[]) // Command line option to test if the binary has been built with GUI support. // Exits immediately. Exits with an error if GUI support has not been built // in. -#ifdef NO_GUI - std::cout << "This version of JackTrip has been built without GUI support." - << std::endl; - std::cout << "(To run JackTrip normally, please omit the --test-gui option.)" - << std::endl; - std::exit(1); -#else - std::cout << "This version of JackTrip has been built with GUI support." - << std::endl; - std::cout << "(To run JackTrip normally, please omit the --test-gui option.)" - << std::endl; - std::exit(0); -#endif + testGui = true; } } - // If we have command line arguments and aren't forcing the GUI run on the command - // line. - if (argc == 1 || forceGui) { #ifdef NO_GUI - if (forceGui) { - std::cout << "This version of jacktrip has not been built with GUI support." - << std::endl; - std::exit(1); - } else { - return new QCoreApplication(argc, argv); - } -#else -#if defined(__unix__) - // Check if X or Wayland environment variables are set. - if (std::getenv("WAYLAND_DISPLAY") == nullptr - && std::getenv("DISPLAY") == nullptr) { - std::cout << "ERROR: Display not found. Make sure X or Wayland is running or " - "try running jacktrip in command line mode." - << std::endl; - std::cout << "(To display a list of command line options run \"jacktrip -h\")" - << std::endl; - std::exit(1); - } -#endif -#if defined(Q_OS_MACOS) && !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Turn on high DPI support. -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) - JTApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif - // Fix for display scaling like 125% or 150% on Windows -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - QGuiApplication::setHighDpiScaleFactorRoundingPolicy( - Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); -#endif // QT_VERSION - - // Initialize webengine - QtWebEngineQuick::initialize(); - // TODO: Add support for QtWebView - // qputenv("QT_WEBVIEW_PLUGIN", "native"); - // QtWebView::initialize(); - - return new JTApplication(argc, argv); -#else - // Turn on high DPI support. -#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); -#endif - // Fix for display scaling like 125% or 150% on Windows -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) -#if defined(NO_VS) && defined(_WIN32) - QGuiApplication::setHighDpiScaleFactorRoundingPolicy( - Qt::HighDpiScaleFactorRoundingPolicy::RoundPreferFloor); + if (testGui) { + std::cout << "This version of JackTrip has been built without GUI support." + << std::endl; + std::cout << "(To run JackTrip normally, please omit the --test-gui option.)" + << std::endl; + std::exit(1); + } + if (forceGui) { + std::cout << "This version of jacktrip has not been built with GUI support." + << std::endl; + std::exit(1); + } #else - QGuiApplication::setHighDpiScaleFactorRoundingPolicy( - Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); -#endif // NO_VS -#endif // QT_VERSION - -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Enables resource sharing between the OpenGL contexts - QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); - // QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); - // QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); - - // QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D11); - QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); - - // Initialize webengine - QtWebEngineQuick::initialize(); - // TODO: Add support for QtWebView - // qputenv("QT_WEBVIEW_PLUGIN", "native"); - // QtWebView::initialize(); -#endif - - return new QApplication(argc, argv); -#endif // Q_OS_MACOS -#endif // NO_GUI - } else { - return new QCoreApplication(argc, argv); + if (testGui) { + std::cout << "This version of JackTrip has been built with GUI support." + << std::endl; + std::cout << "(To run JackTrip normally, please omit the --test-gui option.)" + << std::endl; + std::exit(0); } -} + if (forceGui || argc == 1) { + return UserInterface::createApplication(argc, argv); + } +#endif -void qtMessageHandler([[maybe_unused]] QtMsgType type, - [[maybe_unused]] const QMessageLogContext& context, - const QString& msg) -{ - std::cerr << msg.toStdString() << std::endl; -#ifndef NO_GUI -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Writes to file in order to debug bundles and executables - *ts << msg << Qt::endl; -#endif // NO_VS && QT_VERSION -#endif // NO_GUI + return new QCoreApplication(argc, argv); } void outputError(const QString& msg) @@ -259,64 +145,6 @@ BOOL WINAPI windowsCtrlHandler(DWORD fdwCtrlType) return false; } } - -bool isRunFromCmd() -{ - // Get our parent process pid - HANDLE h = NULL; - PROCESSENTRY32 pe; - ZeroMemory(&pe, sizeof(PROCESSENTRY32)); - DWORD pid = GetCurrentProcessId(); - DWORD ppid = 0; - pe.dwSize = sizeof(PROCESSENTRY32); - h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - if (Process32First(h, &pe)) { - do { - // Loop through the list of processes until we find ours. - if (pe.th32ProcessID == pid) { - ppid = pe.th32ParentProcessID; - break; - } - } while (Process32Next(h, &pe)); - } - CloseHandle(h); - - // Get the name of our parent process; - char pname[MAX_PATH] = {0}; - DWORD size = MAX_PATH; - h = NULL; - h = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, ppid); - if (h) { - if (QueryFullProcessImageNameA(h, 0, pname, &size)) { - CloseHandle(h); - - // Check if our parent process is a command line. - if (size >= 14 && strncmp(pname + size - 14, "powershell.exe", 14) == 0) { - return true; - } - 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; - } - // a few extras for msys/cygwin/etc - if (size >= 8 && strncmp(pname + size - 8, "bash.exe", 8) == 0) { - return true; - } - if (size >= 6 && strncmp(pname + size - 6, "sh.exe", 6) == 0) { - return true; - } - if (size >= 7 && strncmp(pname + size - 7, "zsh.exe", 7) == 0) { - return true; - } - } else { - CloseHandle(h); - } - } - - return false; -} #endif int main(int argc, char* argv[]) @@ -324,217 +152,84 @@ int main(int argc, char* argv[]) QScopedPointer app(createApplication(argc, argv)); QScopedPointer jackTrip; QScopedPointer udpHub; -#ifndef NO_GUI - QSharedPointer window; -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QQuickStyle::setStyle("Basic"); -#endif // QT_VERSION - -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - QSharedPointer vsPtr; - QScopedPointer vsDeeplinkPtr; -#endif // NO_VS && QT_VERSION + QSharedPointer cliSettings; -#if defined(Q_OS_MACOS) && !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - if (qobject_cast(app.data())) { -#else - if (qobject_cast(app.data())) { -#endif - // Start the GUI if there are no command line options. -#ifdef _WIN32 - // Remove the console that appears if we're on windows and not running from a - // command line. - if (!isRunFromCmd()) { - std::cout << "This extra window is caused by a bug in Microsoft Windows. " - << "It can safely be ignored or closed." << std::endl - << std::endl - << "To fix this bug, please upgrade to the latest version of " - << "Windows Terminal available in the Microsoft App Store:" - << std::endl - << "https://aka.ms/terminal" << std::endl; - - FreeConsole(); - } -#endif // _WIN32 - app->setOrganizationName(QStringLiteral("jacktrip")); - app->setOrganizationDomain(QStringLiteral("jacktrip.org")); - app->setApplicationName(QStringLiteral("JackTrip")); - app->setApplicationVersion(gVersion); - - QSharedPointer cliSettings; +#ifndef NO_GUI + QScopedPointer interface; + QApplication* guiApp = dynamic_cast(app.data()); + if (guiApp != nullptr) { + // Start the GUI cliSettings.reset(new Settings(true)); cliSettings->parseInput(argc, argv); + interface.reset(new UserInterface(cliSettings)); + interface->start(guiApp); + return app->exec(); + } +#endif // NO_GUI -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - // Register clipboard Qml type - qmlRegisterType("VS", 1, 0, "Clipboard"); - - // prepare handler for deeplinks jacktrip://join/ - vsDeeplinkPtr.reset(new VsDeeplink(cliSettings->getDeeplink())); - if (!vsDeeplinkPtr->getDeeplink().isEmpty()) { - bool readyForExit = vsDeeplinkPtr->waitForReady(); - if (readyForExit) - return 0; - } - - // Check which mode we are running in - QSettings settings; - int uiMode = QJackTrip::UNSET; - if (!vsDeeplinkPtr->getDeeplink().isEmpty()) { - uiMode = QJackTrip::VIRTUAL_STUDIO; - } else if (cliSettings->guiForceClassicMode()) { - uiMode = QJackTrip::STANDARD; - // force settings change; otherwise, virtual studio - // window will still be displayed - settings.setValue(QStringLiteral("UiMode"), uiMode); - } else { - uiMode = settings.value(QStringLiteral("UiMode"), QJackTrip::UNSET).toInt(); - } - - window.reset(new QJackTrip(cliSettings, !vsDeeplinkPtr->getDeeplink().isEmpty())); -#else - window.reset(new QJackTrip(cliSettings)); -#endif // NO_VS - QObject::connect(window.data(), &QJackTrip::signalExit, app.data(), - &QCoreApplication::quit, Qt::QueuedConnection); - -#if !defined(NO_VS) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) - vsPtr.reset(new VirtualStudio(uiMode == QJackTrip::UNSET)); - QObject::connect(vsPtr.data(), &VirtualStudio::signalExit, app.data(), - &QCoreApplication::quit, Qt::QueuedConnection); - vsPtr->setStandardWindow(window); - vsPtr->setCLISettings(cliSettings); - window->setVs(vsPtr); - QObject::connect(vsDeeplinkPtr.get(), &VsDeeplink::signalDeeplink, vsPtr.get(), - &VirtualStudio::handleDeeplinkRequest, Qt::QueuedConnection); - vsDeeplinkPtr->readyForSignals(); - - if (uiMode == QJackTrip::UNSET) { - vsPtr->show(); - } else if (uiMode == QJackTrip::VIRTUAL_STUDIO) { - vsPtr->show(); - } else { - window->show(); - } - - // Log to file - QString logPath( - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); - QDir logDir; - if (!logDir.exists(logPath)) { - logDir.mkpath(logPath); - } - QString fileLoc(logPath.append("/log.txt")); - qDebug() << "Log file location:" << fileLoc; - outFile.setFileName(fileLoc); - if (!outFile.open(QIODevice::WriteOnly | QIODevice::Append)) { - qDebug() << "Log file open failed:" << outFile.errorString(); - } - ts = new QTextStream(&outFile); - qInstallMessageHandler(qtMessageHandler); -#else - window->show(); -#endif // NO_VS + // Otherwise use the non-GUI version, and parse our command line. + try { + cliSettings.reset(new Settings(false)); + cliSettings->parseInput(argc, argv); -#if !defined(NO_UPDATER) && !defined(__unix__) #ifndef PSI -#if defined(NO_VS) || QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - - // This wasn't set up earlier in NO_VS builds. Create it here. - QSettings settings; -#endif - QString updateChannel = settings.value(QStringLiteral("UpdateChannel"), "stable") - .toString() - .toLower(); - QString baseUrl = QStringLiteral("https://files.jacktrip.org/app-releases/%1") - .arg(updateChannel); -#else - QString baseUrl = QStringLiteral("https://nuages.psi-borg.org/jacktrip"); -#endif // PSI - // Setup auto-update feed - dblsqd::Feed* feed = new dblsqd::Feed(); -#ifdef Q_OS_WIN - feed->setUrl(QUrl(QString("%1/%2-manifests.json").arg(baseUrl, "win"))); -#endif -#ifdef Q_OS_MACOS - feed->setUrl(QUrl(QString("%1/%2-manifests.json").arg(baseUrl, "mac"))); -#endif - if (feed) { - dblsqd::UpdateDialog* updateDialog = new dblsqd::UpdateDialog(feed); - updateDialog->setIcon(":/qjacktrip/icon.png"); + if (gVerboseFlag) { + QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true")); } -#endif // NO_UPDATER - } else { -#endif // NO_GUI - // Otherwise use the non-GUI version, and parse our command line. - try { - Settings settings; - settings.parseInput(argc, argv); - -#ifndef PSI - if (gVerboseFlag) { - QLoggingCategory::setFilterRules(QStringLiteral("*.debug=true")); - } #endif - // Either start our hub server or our jacktrip process as appropriate. - if (settings.isHubServer()) { - udpHub.reset(settings.getConfiguredHubServer()); - if (gVerboseFlag) - std::cout << "Settings:startJackTrip before udphub->start" - << std::endl; - QObject::connect(udpHub.data(), &UdpHubListener::signalStopped, - app.data(), &QCoreApplication::quit, - Qt::QueuedConnection); - QObject::connect(udpHub.data(), &UdpHubListener::signalError, - outputError); - QObject::connect(udpHub.data(), &UdpHubListener::signalError, app.data(), - &QCoreApplication::quit, Qt::QueuedConnection); + // Either start our hub server or our jacktrip process as appropriate. + if (cliSettings->isHubServer()) { + udpHub.reset(cliSettings->getConfiguredHubServer()); + if (gVerboseFlag) + std::cout << "Settings:startJackTrip before udphub->start" << std::endl; + QObject::connect(udpHub.data(), &UdpHubListener::signalStopped, app.data(), + &QCoreApplication::quit, Qt::QueuedConnection); + QObject::connect(udpHub.data(), &UdpHubListener::signalError, outputError); + QObject::connect(udpHub.data(), &UdpHubListener::signalError, app.data(), + &QCoreApplication::quit, Qt::QueuedConnection); #ifndef _WIN32 - setupUnixSignalHandler(UdpHubListener::sigIntHandler); + setupUnixSignalHandler(UdpHubListener::sigIntHandler); #else isHubServer = true; SetConsoleCtrlHandler(windowsCtrlHandler, true); -#endif - udpHub->start(); - } else { - jackTrip.reset(settings.getConfiguredJackTrip()); - if (gVerboseFlag) - std::cout << "Settings:startJackTrip before mJackTrip->startProcess" - << std::endl; - QObject::connect(jackTrip.data(), &JackTrip::signalProcessesStopped, - app.data(), &QCoreApplication::quit, - Qt::QueuedConnection); - QObject::connect(jackTrip.data(), &JackTrip::signalError, outputError); - QObject::connect(jackTrip.data(), &JackTrip::signalError, app.data(), - &QCoreApplication::quit, Qt::QueuedConnection); +#endif // _WIN32 + udpHub->start(); + } else { + jackTrip.reset(cliSettings->getConfiguredJackTrip()); + if (gVerboseFlag) + std::cout << "Settings:startJackTrip before mJackTrip->startProcess" + << std::endl; + QObject::connect(jackTrip.data(), &JackTrip::signalProcessesStopped, + app.data(), &QCoreApplication::quit, Qt::QueuedConnection); + QObject::connect(jackTrip.data(), &JackTrip::signalError, outputError); + QObject::connect(jackTrip.data(), &JackTrip::signalError, app.data(), + &QCoreApplication::quit, Qt::QueuedConnection); + #ifndef _WIN32 - setupUnixSignalHandler(JackTrip::sigIntHandler); + setupUnixSignalHandler(JackTrip::sigIntHandler); #else std::cout << SetConsoleCtrlHandler(windowsCtrlHandler, true) << std::endl; -#endif -#ifdef WAIRTOHUB // WAIR - jackTrip->startProcess( - 0); // for WAIR compatibility, ID in jack client name +#endif // _WIN32 + +#ifdef WAIRTOHUB // WAIR + jackTrip->startProcess(0); // for WAIR compatibility, ID in jack client name #else jackTrip->startProcess(); #endif // endwhere - } - - if (gVerboseFlag) - std::cout << "step 6" << std::endl; - if (gVerboseFlag) - std::cout << "jmain before app->exec()" << std::endl; - } catch (const std::exception& e) { - std::cerr << "ERROR:" << std::endl; - std::cerr << e.what() << std::endl; - std::cerr << "Exiting JackTrip..." << std::endl; - std::cerr << gPrintSeparator << std::endl; - return -1; } -#ifndef NO_GUI + + if (gVerboseFlag) + std::cout << "step 6" << std::endl; + if (gVerboseFlag) + std::cout << "jmain before app->exec()" << std::endl; + } catch (const std::exception& e) { + std::cerr << "ERROR:" << std::endl; + std::cerr << e.what() << std::endl; + std::cerr << "Exiting JackTrip..." << std::endl; + std::cerr << gPrintSeparator << std::endl; + return -1; } -#endif // NO_GUI return app->exec(); } diff --git a/src/vs/AboutWindow.qml b/src/vs/AboutWindow.qml new file mode 100644 index 0000000..da21499 --- /dev/null +++ b/src/vs/AboutWindow.qml @@ -0,0 +1,145 @@ +import QtQuick +import QtQuick.Controls + +Window { + id: aboutWindow + title: "About JackTrip" + visible: false + + width: 600 * virtualstudio.uiScale + height: 570 * virtualstudio.uiScale + + property int fontTitle: 20 + property int fontMedium: 12 + property int fontSmall: 10 + property int fontTiny: 8 + + property string buttonColour: "#F2F3F3" + property string buttonHoverColour: "#E7E8E8" + property string buttonPressedColour: "#E7E8E8" + property string buttonStroke: "#EAEBEB" + property string buttonHoverStroke: "#B0B5B5" + property string buttonPressedStroke: "#B0B5B5" + + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + property string textAreaTextColour: virtualstudio.darkMode ? "#A6A6A6" : "#757575" + property string textAreaColour: virtualstudio.darkMode ? "#494646" : "#EAECEC" + + Rectangle { + width: parent.width + height: parent.height + color: backgroundColour + + Rectangle { + id: aboutJackTripLogo + x: 0; y: 0; + width: 122 * virtualstudio.uiScale + height: 108 * virtualstudio.uiScale + color: backgroundColour + Image { + id: aboutLogoImage + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + source: "logo.svg" + width: 42 * virtualstudio.uiScale; height: 76 * virtualstudio.uiScale + sourceSize: Qt.size(aboutLogoImage.width,aboutLogoImage.height) + fillMode: Image.PreserveAspectFit + smooth: true + } + } + + Item { + id: aboutContent + x: 122 * virtualstudio.uiScale; + y: 0; + width: parent.width - aboutJackTripLogo.width - (32 * virtualstudio.uiScale) + height: parent.height + + Text { + id: aboutHeader + anchors.top: parent.top + anchors.topMargin: 16 * virtualstudio.uiScale + anchors.left: parent.left + width: parent.width + text: "JackTrip Desktop App" + font {family: "Poppins"; pixelSize: fontTitle * virtualstudio.fontScale * virtualstudio.uiScale; bold: true } + color: textColour + elide: Text.ElideRight + wrapMode: Text.WordWrap + } + + Text { + id: aboutVersion + anchors.top: aboutHeader.bottom + anchors.topMargin: 16 * virtualstudio.uiScale + anchors.left: parent.left + width: parent.width + text: "Version " + virtualstudio.versionString + font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale; bold: true } + color: textColour + elide: Text.ElideRight + wrapMode: Text.WordWrap + } + + Text { + id: aboutBuildInfo + anchors.top: aboutVersion.bottom + anchors.topMargin: 16 * virtualstudio.uiScale + anchors.left: parent.left + width: parent.width + text: virtualstudio.buildString + font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale; } + color: textColour + elide: Text.ElideRight + wrapMode: Text.WordWrap + } + + Text { + id: aboutCopyright + anchors.top: aboutBuildInfo.bottom + anchors.topMargin: 16 * virtualstudio.uiScale + anchors.left: parent.left + width: parent.width + text: virtualstudio.copyrightString + font {family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale; } + color: textColour + elide: Text.ElideRight + wrapMode: Text.WordWrap + } + + Button { + id: aboutCloseButton + anchors.top: aboutCopyright.bottom + anchors.topMargin: 16 * virtualstudio.uiScale + anchors.left: parent.left + width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale + onClicked: () => { + aboutWindow.visible = false; + } + + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: aboutCloseButton.down ? buttonPressedColour : (aboutCloseButton.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: aboutCloseButton.down ? buttonPressedStroke : (aboutCloseButton.hovered ? buttonHoverStroke : buttonStroke) + } + + Text { + text: "Close" + font.family: "Poppins" + font.pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + + Connections { + target: virtualstudio + + function onOpenAboutWindow() { + aboutWindow.visible = true; + } + } +} diff --git a/src/gui/AppIcon.qml b/src/vs/AppIcon.qml similarity index 100% rename from src/gui/AppIcon.qml rename to src/vs/AppIcon.qml diff --git a/src/gui/AudioInterfaceMode.h b/src/vs/AudioInterfaceMode.h similarity index 96% rename from src/gui/AudioInterfaceMode.h rename to src/vs/AudioInterfaceMode.h index 3416b87..b1aaebe 100644 --- a/src/gui/AudioInterfaceMode.h +++ b/src/vs/AudioInterfaceMode.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/AudioSettings.qml b/src/vs/AudioSettings.qml similarity index 100% rename from src/gui/AudioSettings.qml rename to src/vs/AudioSettings.qml diff --git a/src/gui/Browse.qml b/src/vs/Browse.qml similarity index 71% rename from src/gui/Browse.qml rename to src/vs/Browse.qml index d632152..4974347 100644 --- a/src/gui/Browse.qml +++ b/src/vs/Browse.qml @@ -10,8 +10,6 @@ Item { color: backgroundColour } - property bool refreshing: false - property int buttonHeight: 25 property int buttonWidth: 103 property int extraSettingsButtonWidth: 16 @@ -42,19 +40,6 @@ Item { virtualstudio.refreshStudios(currentIndex, true) } - 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: footer Rectangle { @@ -96,13 +81,13 @@ Item { SectionHeading { id: emptyListSectionHeading listIsEmpty: true - visible: parent.count == 0 && !virtualstudio.showCreateStudio + visible: parent.count == 0 } Text { id: emptyListMessage - visible: parent.count == 0 && !virtualstudio.showCreateStudio - text: "No studios found that match your filter criteria." + visible: parent.count == 0 + text: virtualstudio.refreshInProgress ? "Loading Studios..." : "No studios found that match your filter criteria." font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } color: textColour width: emptyListMessageWidth @@ -120,11 +105,10 @@ Item { border.width: 1 border.color: resetFiltersButton.down ? buttonPressedStroke : (resetFiltersButton.hovered ? buttonHoverStroke : buttonStroke) } - visible: parent.count == 0 && !virtualstudio.showCreateStudio + visible: parent.count == 0 onClicked: { - virtualstudio.showSelfHosted = false; + virtualstudio.showSelfHosted = true; virtualstudio.showInactive = true; - refreshing = true; refresh(); } anchors.top: emptyListMessage.bottom @@ -139,71 +123,6 @@ Item { } } - Rectangle { - id: newUserEmptyList - anchors.fill: parent - color: "transparent" - visible: parent.count == 0 && virtualstudio.showCreateStudio - - Rectangle { - color: "transparent" - width: emptyListMessageWidth - height: createButton.height + createStudioMessage.height + welcomeMessage.height + createButtonTopMargin + createMessageTopMargin - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - - Text { - id: welcomeMessage - text: "Welcome" - font { family: "Poppins"; pixelSize: fontBig * virtualstudio.fontScale * virtualstudio.uiScale; weight: Font.Bold } - color: textColour - width: emptyListMessageWidth - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: parent.top - } - - Text { - id: createStudioMessage - text: "Looks like you're not a member of any studios!\nHave the studio owner send you an invite link, or create your own studio to invite others." - font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } - color: textColour - width: emptyListMessageWidth - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: welcomeMessage.bottom - anchors.topMargin: createMessageTopMargin - } - - Button { - id: createButton - background: Rectangle { - radius: 6 * virtualstudio.uiScale - color: createButton.down ? "#E7E8E8" : "#F2F3F3" - border.width: 1 - border.color: createButton.down ? "#B0B5B5" : createButtonStroke - layer.enabled: createButton.hovered && !createButton.down - } - onClicked: { virtualstudio.createStudio(); } - anchors.top: createStudioMessage.bottom - anchors.topMargin: createButtonTopMargin - anchors.horizontalCenter: createStudioMessage.horizontalCenter - width: 210 * virtualstudio.uiScale; height: 45 * virtualstudio.uiScale - Text { - text: "Create a Studio" - font.family: "Poppins" - font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale - font.weight: Font.Bold - color: "#DB0A0A" - anchors.horizontalCenter: parent.horizontalCenter - anchors.verticalCenter: parent.verticalCenter - } - } - } - } - // Disable momentum scroll MouseArea { z: -1 @@ -243,7 +162,7 @@ Item { border.width: 1 border.color: refreshButton.down ? buttonPressedStroke : (refreshButton.hovered ? buttonHoverStroke : buttonStroke) } - onClicked: { refreshing = true; refresh() } + onClicked: { refresh() } anchors.verticalCenter: parent.verticalCenter x: 16 * virtualstudio.uiScale width: buttonWidth * virtualstudio.uiScale; height: buttonHeight * virtualstudio.uiScale @@ -304,6 +223,9 @@ Item { } } + AboutWindow { + } + FeedbackSurvey { } @@ -316,13 +238,11 @@ Item { 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/ChangeDevices.qml b/src/vs/ChangeDevices.qml similarity index 98% rename from src/gui/ChangeDevices.qml rename to src/vs/ChangeDevices.qml index 7937ec2..1a8dcfe 100644 --- a/src/gui/ChangeDevices.qml +++ b/src/vs/ChangeDevices.qml @@ -15,7 +15,7 @@ Rectangle { property int bottomToolTipMargin: 8 property int rightToolTipMargin: 4 - property string saveButtonText: "#DB0A0A" + property string saveButtonText: "#000000" property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" property string meterColor: virtualstudio.darkMode ? "gray" : "#E0E0E0" property real muteButtonLightnessValue: virtualstudio.darkMode ? 1.0 : 0.0 diff --git a/src/gui/Connected.qml b/src/vs/Connected.qml similarity index 98% rename from src/gui/Connected.qml rename to src/vs/Connected.qml index 00d6798..b95af9f 100644 --- a/src/gui/Connected.qml +++ b/src/vs/Connected.qml @@ -39,7 +39,7 @@ Item { property string saveButtonPressedColour: "#E7E8E8" property string saveButtonStroke: "#EAEBEB" property string saveButtonPressedStroke: "#B0B5B5" - property string saveButtonText: "#DB0A0A" + property string saveButtonText: "#000000" property string muteButtonMutedColor: "#FCB6B6" property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" diff --git a/src/gui/CreateStudio.qml b/src/vs/CreateStudio.qml similarity index 94% rename from src/gui/CreateStudio.qml rename to src/vs/CreateStudio.qml index 4228f51..7458dd6 100644 --- a/src/gui/CreateStudio.qml +++ b/src/vs/CreateStudio.qml @@ -41,7 +41,7 @@ Item { settings.javascriptCanPaste: true settings.screenCaptureEnabled: true profile.httpUserAgent: `JackTrip/${virtualstudio.versionString}` - url: `https://${virtualstudio.apiHost}/qt/create?accessToken=${accessToken}` + url: `https://${virtualstudio.apiHost === "test.jacktrip.com" ? "next-test.jacktrip.com" : "www.jacktrip.com"}/app/studios/create?accessToken=${accessToken}&userId=${auth.userId}` onContextMenuRequested: function(request) { // this disables the default context menu: https://doc.qt.io/qt-6.2/qml-qtwebengine-contextmenurequest.html#accepted-prop @@ -87,6 +87,7 @@ Item { RowLayout { id: layout anchors.fill: parent + visible: virtualstudio.serverModel.length > 0 Item { Layout.fillHeight: true diff --git a/src/gui/DeviceControls.qml b/src/vs/DeviceControls.qml similarity index 100% rename from src/gui/DeviceControls.qml rename to src/vs/DeviceControls.qml diff --git a/src/gui/DeviceControlsGroup.qml b/src/vs/DeviceControlsGroup.qml similarity index 99% rename from src/gui/DeviceControlsGroup.qml rename to src/vs/DeviceControlsGroup.qml index 43ca5fd..dc250c8 100644 --- a/src/gui/DeviceControlsGroup.qml +++ b/src/vs/DeviceControlsGroup.qml @@ -4,7 +4,7 @@ import QtQuick.Layouts Rectangle { property string disabledButtonText: "#D3D4D4" - property string saveButtonText: "#DB0A0A" + property string saveButtonText: "#000000" property int fullHeight: 88 * virtualstudio.uiScale property int minimumHeight: 48 * virtualstudio.uiScale diff --git a/src/gui/DeviceRefreshButton.qml b/src/vs/DeviceRefreshButton.qml similarity index 100% rename from src/gui/DeviceRefreshButton.qml rename to src/vs/DeviceRefreshButton.qml diff --git a/src/gui/DeviceWarning.qml b/src/vs/DeviceWarning.qml similarity index 80% rename from src/gui/DeviceWarning.qml rename to src/vs/DeviceWarning.qml index 4b8302a..da00b20 100644 --- a/src/gui/DeviceWarning.qml +++ b/src/vs/DeviceWarning.qml @@ -5,6 +5,22 @@ Item { height: 28 * virtualstudio.uiScale property string devicesWarningColour: "#F21B1B" + function getTooltip () { + var result = ""; + if (Boolean(audio.devicesError)) { + result = audio.devicesError; + if (audio.devicesErrorHelpUrl) { + result += " Click for more info." + } + } else if (Boolean(audio.devicesWarning)) { + result = audio.devicesWarning; + if (audio.devicesWarningHelpUrl) { + result += " Click for more info." + } + } + return result; + } + AppIcon { id: devicesWarningIcon anchors.left: parent.left @@ -32,7 +48,7 @@ Item { anchors.left: warningOrErrorText.right anchors.leftMargin: 2 * virtualstudio.uiScale anchors.top: devicesWarningIcon.top - content: qsTr(audio.devicesError || audio.devicesWarning) + content: getTooltip() iconColor: devicesWarningColour size: 16 * virtualstudio.uiScale visible: Boolean(audio.devicesError) || Boolean(audio.devicesWarning) diff --git a/src/vs/DeviceWarningModal.qml b/src/vs/DeviceWarningModal.qml new file mode 100644 index 0000000..f56dfa7 --- /dev/null +++ b/src/vs/DeviceWarningModal.qml @@ -0,0 +1,164 @@ +import QtQuick +import QtQuick.Controls + +Item { + anchors.centerIn: parent + width: 480 * virtualstudio.uiScale + + property int fontMedium: 12 + property int fontSmall: 10 + + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + property string buttonColour: virtualstudio.darkMode ? "#494646" : "#EAECEC" + property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC" + property string buttonPressedColour: virtualstudio.darkMode ? "#524F4F" : "#DEE0E0" + property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#34979797" + property string buttonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC" + property string buttonHoverColour: virtualstudio.darkMode ? "#5B5858" : "#D3D4D4" + property string devicesWarningColour: "#F21B1B" + + Popup { + id: deviceWarningPopup + padding: 1 + width: parent.width + height: 350 * virtualstudio.uiScale + anchors.centerIn: parent + modal: true + focus: true + + background: Rectangle { + anchors.fill: parent + color: "transparent" + radius: 6 * virtualstudio.uiScale + border.width: 1 + border.color: buttonStroke + clip: true + } + + contentItem: Rectangle { + width: parent.width + height: parent.height + color: backgroundColour + radius: 6 * virtualstudio.uiScale + + Item { + id: deviceWarningPopupContent + anchors.top: parent.top + anchors.topMargin: 24 * virtualstudio.uiScale + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + + AppIcon { + id: devicesWarningIcon + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + width: 56 * virtualstudio.uiScale + height: 56 * virtualstudio.uiScale + icon.source: "warning.svg" + color: devicesWarningColour + } + + Text { + id: deviceWarningPopupHeader + anchors.top: devicesWarningIcon.bottom + anchors.topMargin: 16 * virtualstudio.uiScale + width: parent.width + text: "Audio Configuration Warning" + font {family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale; bold: true } + horizontalAlignment: Text.AlignHCenter + color: textColour + elide: Text.ElideRight + wrapMode: Text.WordWrap + } + + Text { + id: devicesWarningText + anchors.top: deviceWarningPopupHeader.bottom + anchors.topMargin: 16 * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + width: parent.width - (32 * virtualstudio.uiScale) + text: qsTr(audio.devicesWarning) + font {family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale } + horizontalAlignment: Text.AlignHCenter + color: textColour + elide: Text.ElideRight + wrapMode: Text.WordWrap + } + + LearnMoreButton { + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: devicesWarningText.bottom + anchors.topMargin: 24 * virtualstudio.uiScale + url: Boolean(audio.devicesErrorHelpUrl) ? audio.devicesErrorHelpUrl : audio.devicesWarningHelpUrl + visible: Boolean(audio.devicesErrorHelpUrl) || Boolean(audio.devicesWarningHelpUrl) + } + + Button { + id: backButton + anchors.left: parent.left + anchors.leftMargin: 24 * virtualstudio.uiScale + anchors.bottom: parent.bottom + anchors.bottomMargin: 24 * virtualstudio.uiScale + width: 160 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale + onClicked: () => { + deviceWarningPopup.close(); + } + + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: backButton.down ? buttonPressedColour : (backButton.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: backButton.down ? buttonPressedStroke : (backButton.hovered ? buttonHoverStroke : buttonStroke) + } + + Text { + text: "Back to Settings" + font.family: "Poppins" + font.pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale + color: textColour + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + + Button { + id: connectButton + anchors.right: parent.right + anchors.rightMargin: 24 * virtualstudio.uiScale + anchors.bottom: parent.bottom + anchors.bottomMargin: 24 * virtualstudio.uiScale + width: 160 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale + onClicked: () => { + deviceWarningPopup.close(); + audio.stopAudio(true); + virtualstudio.studioToJoin = virtualstudio.currentStudio.id; + virtualstudio.windowState = "connected"; + virtualstudio.saveSettings(); + virtualstudio.joinStudio(); + } + + background: Rectangle { + radius: 6 * virtualstudio.uiScale + color: connectButton.down ? buttonPressedColour : (connectButton.hovered ? buttonHoverColour : buttonColour) + border.width: 1 + border.color: connectButton.down ? buttonPressedStroke : (connectButton.hovered ? buttonHoverStroke : buttonStroke) + } + + Text { + text: "Connect to Session" + font.family: "Poppins" + font.pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale + color: textColour + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + } + } + } + } + } + + function open () { + deviceWarningPopup.open(); + } +} diff --git a/src/gui/Failed.qml b/src/vs/Failed.qml similarity index 100% rename from src/gui/Failed.qml rename to src/vs/Failed.qml diff --git a/src/gui/FeedbackSurvey.qml b/src/vs/FeedbackSurvey.qml similarity index 99% rename from src/gui/FeedbackSurvey.qml rename to src/vs/FeedbackSurvey.qml index 6c1528d..05731f3 100644 --- a/src/gui/FeedbackSurvey.qml +++ b/src/vs/FeedbackSurvey.qml @@ -325,7 +325,7 @@ Item { font.family: "Poppins" font.pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale font.weight: Font.Bold - color: "#DB0A0A" + color: "#000000" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } diff --git a/src/gui/FirstLaunch.qml b/src/vs/FirstLaunch.qml similarity index 95% rename from src/gui/FirstLaunch.qml rename to src/vs/FirstLaunch.qml index 70d8f1c..e5623b5 100644 --- a/src/gui/FirstLaunch.qml +++ b/src/vs/FirstLaunch.qml @@ -52,7 +52,7 @@ Item { border.color: vsButton.down ? buttonPressedStroke : (vsButton.hovered ? buttonHoverStroke : buttonStroke) layer.enabled: vsButton.hovered && !vsButton.down } - onClicked: { virtualstudio.showFirstRun = false; virtualstudio.windowState = "login"; virtualstudio.toVirtualStudio(); } + onClicked: { virtualstudio.toVirtualStudioMode(); } x: parent.width / 2 - (265 * virtualstudio.uiScale); y: 290 * virtualstudio.uiScale width: 234 * virtualstudio.uiScale; height: 49 * virtualstudio.uiScale Text { @@ -60,7 +60,7 @@ Item { font.family: "Poppins" font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale font.weight: Font.Bold - color: "#DB0A0A" + color: "#000000" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } @@ -93,7 +93,7 @@ Item { border.color: standardButton.down ? buttonPressedStroke : (standardButton.hovered ? buttonHoverStroke : buttonStroke) layer.enabled: standardButton.hovered && !standardButton.down } - onClicked: { virtualstudio.toStandard(); } + onClicked: { virtualstudio.toClassicMode(); } x: parent.width / 2 + (32 * virtualstudio.uiScale); y: 290 * virtualstudio.uiScale width: 234 * virtualstudio.uiScale; height: 49 * virtualstudio.uiScale Text { @@ -101,7 +101,7 @@ Item { font.family: "Poppins" font.pixelSize: 18 * virtualstudio.fontScale * virtualstudio.uiScale font.weight: Font.Bold - color: "#DB0A0A" + color: "#000000" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } diff --git a/src/gui/Footer.qml b/src/vs/Footer.qml similarity index 100% rename from src/gui/Footer.qml rename to src/vs/Footer.qml diff --git a/src/gui/InfoTooltip.qml b/src/vs/InfoTooltip.qml similarity index 100% rename from src/gui/InfoTooltip.qml rename to src/vs/InfoTooltip.qml diff --git a/src/JTApplication.h b/src/vs/JTApplication.h similarity index 94% rename from src/JTApplication.h rename to src/vs/JTApplication.h index 7d27b52..8b62c2e 100644 --- a/src/JTApplication.h +++ b/src/vs/JTApplication.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/JTOriginal.png b/src/vs/JTOriginal.png similarity index 100% rename from src/gui/JTOriginal.png rename to src/vs/JTOriginal.png diff --git a/src/gui/JTVS.png b/src/vs/JTVS.png similarity index 100% rename from src/gui/JTVS.png rename to src/vs/JTVS.png diff --git a/src/gui/LearnMoreButton.qml b/src/vs/LearnMoreButton.qml similarity index 60% rename from src/gui/LearnMoreButton.qml rename to src/vs/LearnMoreButton.qml index aa26177..e9a4a78 100644 --- a/src/gui/LearnMoreButton.qml +++ b/src/vs/LearnMoreButton.qml @@ -8,6 +8,14 @@ Button { width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale + property string textColour: virtualstudio.darkMode ? "#FAFBFB" : "#0F0D0D" + property string buttonColour: virtualstudio.darkMode ? "#494646" : "#EAECEC" + property string buttonPressedStroke: virtualstudio.darkMode ? "#827D7D" : "#BABCBC" + property string buttonPressedColour: virtualstudio.darkMode ? "#524F4F" : "#DEE0E0" + property string buttonStroke: virtualstudio.darkMode ? "#80827D7D" : "#34979797" + property string buttonHoverStroke: virtualstudio.darkMode ? "#7B7777" : "#BABCBC" + property string buttonHoverColour: virtualstudio.darkMode ? "#5B5858" : "#D3D4D4" + onClicked: { virtualstudio.openLink(url); } diff --git a/src/gui/Login.qml b/src/vs/Login.qml similarity index 88% rename from src/gui/Login.qml rename to src/vs/Login.qml index d52400b..7ca4dd0 100644 --- a/src/gui/Login.qml +++ b/src/vs/Login.qml @@ -59,6 +59,23 @@ Item { id: clipboard } + function getVerificationCodeText() { + if (Boolean(auth.verificationCode)) { + return auth.verificationCode; + } + if (numFailures < 5) { + return "Loading..."; + } + var result; + if (auth.errorMessage.startsWith("Host") && auth.errorMessage.endsWith("not found")) { + result = "Your Internet connection is offline."; + } else { + result = "There was an error trying to sign in."; + } + result += "
Please try again."; + return result; + } + Item { id: loginScreenHeader anchors.horizontalCenter: parent.horizontalCenter @@ -107,7 +124,7 @@ Item { anchors.horizontalCenter: parent.horizontalCenter y: 128 * virtualstudio.uiScale width: 500 * virtualstudio.uiScale; - visible: true + visible: Boolean(auth.verificationCode) color: textColour wrapMode: Text.WordWrap horizontalAlignment: Text.AlignHCenter @@ -133,13 +150,13 @@ Item { Text { id: deviceVerificationCode - text: auth.verificationCode || ((numFailures >= 5) ? "Error" : "Loading..."); + text: getVerificationCodeText() font.family: "Poppins" font.pixelSize: 20 * virtualstudio.fontScale * virtualstudio.uiScale font.letterSpacing: Boolean(auth.verificationCode) ? 8 : 1 anchors.horizontalCenter: parent.horizontalCenter y: 196 * virtualstudio.uiScale - width: 360 * virtualstudio.uiScale; + width: 540 * virtualstudio.uiScale; visible: !auth.isAuthenticated color: Boolean(auth.verificationCode) ? textColour : disabledButtonText wrapMode: Text.WordWrap @@ -215,18 +232,19 @@ Item { anchors.verticalCenter: parent.verticalCenter color: loginButton.down ? buttonTextPressed : (loginButton.hovered ? buttonTextHover : buttonTextColour) } - visible: !auth.isAuthenticated + visible: !auth.isAuthenticated && Boolean(auth.verificationCode) } Text { id: authFailedText - text: "There was an error trying to sign in. Please try again." + text: auth.errorMessage font.family: "Poppins" font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + horizontalAlignment: Text.AlignHCenter anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: loginScreenFooter.top anchors.bottomMargin: 16 * virtualstudio.uiScale - visible: (loginScreen.state === "failed" || numFailures > 0) && loginScreen.state !== "success" + visible: (loginScreen.state === "failed" && numFailures >= 5) && loginScreen.state !== "success" color: errorTextColour } @@ -264,12 +282,12 @@ Item { Item { id: resetCodeButton - visible: true + visible: loginScreen.state == "failed" || (!auth.isAuthenticated && auth.verificationCode) x: parent.showBackButton ? (parent.x + parent.width / 2) + 8 * virtualstudio.uiScale : (parent.x + parent.width / 2) - resetCodeButton.width / 2 anchors.verticalCenter: parent.verticalCenter width: 144 * virtualstudio.uiScale; height: 32 * virtualstudio.uiScale Text { - text: "Reset Code" + text: auth.verificationCode ? "Reset Code" : "Retry" font.family: "Poppins" font.underline: true font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale @@ -280,8 +298,11 @@ Item { MouseArea { anchors.fill: parent onClicked: () => { - if (auth.verificationCode && auth.verificationUrl) { + if (auth.verificationCode) { auth.resetCode(); + } else { + numFailures = 0; + virtualstudio.login(); } } cursorShape: Qt.PointingHandCursor @@ -310,14 +331,22 @@ Item { } } + Timer { + id: retryTimer + running: false + repeat: false + interval: 300 + onTriggered: { virtualstudio.login(); } + } + Connections { target: auth function onUpdatedAuthenticationStage (stage) { loginScreen.state = stage; if (stage === "failed") { numFailures = numFailures + 1; - if (numFailures < 5 && !virtualstudio.hasRefreshToken) { - virtualstudio.login(); + if (numFailures < 5) { + retryTimer.restart(); } } if (stage === "success") { diff --git a/src/gui/Meter.qml b/src/vs/Meter.qml similarity index 100% rename from src/gui/Meter.qml rename to src/vs/Meter.qml diff --git a/src/gui/MeterBars.qml b/src/vs/MeterBars.qml similarity index 100% rename from src/gui/MeterBars.qml rename to src/vs/MeterBars.qml diff --git a/src/gui/Permissions.qml b/src/vs/Permissions.qml similarity index 98% rename from src/gui/Permissions.qml rename to src/vs/Permissions.qml index da538c6..a4a5b14 100644 --- a/src/gui/Permissions.qml +++ b/src/vs/Permissions.qml @@ -10,9 +10,11 @@ Item { property int fontSmall: 11 property int fontExtraSmall: 8 + property string saveButtonPressedColour: "#E7E8E8" + property string saveButtonPressedStroke: "#B0B5B5" property string saveButtonBackgroundColour: "#F2F3F3" property string saveButtonStroke: "#EAEBEB" - property string saveButtonText: "#DB0A0A" + property string saveButtonText: "#000000" Item { id: requestMicPermissionsItem diff --git a/src/gui/Poppins-Bold.ttf b/src/vs/Poppins-Bold.ttf similarity index 100% rename from src/gui/Poppins-Bold.ttf rename to src/vs/Poppins-Bold.ttf diff --git a/src/gui/Poppins-Regular.ttf b/src/vs/Poppins-Regular.ttf similarity index 100% rename from src/gui/Poppins-Regular.ttf rename to src/vs/Poppins-Regular.ttf diff --git a/src/gui/Prompt.svg b/src/vs/Prompt.svg similarity index 100% rename from src/gui/Prompt.svg rename to src/vs/Prompt.svg diff --git a/src/gui/Recommendations.qml b/src/vs/Recommendations.qml similarity index 99% rename from src/gui/Recommendations.qml rename to src/vs/Recommendations.qml index 5e5fd57..43d98be 100644 --- a/src/gui/Recommendations.qml +++ b/src/vs/Recommendations.qml @@ -28,8 +28,7 @@ Item { property string saveButtonPressedColour: "#E7E8E8" property string saveButtonStroke: "#EAEBEB" property string saveButtonPressedStroke: "#B0B5B5" - property string recommendationText: "#DB0A0A" - property string saveButtonText: "#DB0A0A" + property string saveButtonText: "#000000" property string checkboxStroke: "#0062cc" property string checkboxPressedStroke: "#007AFF" property string disabledButtonText: "#D3D4D4" diff --git a/src/gui/SectionHeading.qml b/src/vs/SectionHeading.qml similarity index 97% rename from src/gui/SectionHeading.qml rename to src/vs/SectionHeading.qml index 84568df..474f0b5 100644 --- a/src/gui/SectionHeading.qml +++ b/src/vs/SectionHeading.qml @@ -32,17 +32,17 @@ Rectangle { border.color: createButton.down ? "#B0B5B5" : "#EAEBEB" layer.enabled: createButton.hovered && !createButton.down } - onClicked: { virtualstudio.createStudio(); } + onClicked: { virtualstudio.windowState = "create_studio"; } anchors.right: filterButton.left anchors.rightMargin: 16 anchors.verticalCenter: sectionText.verticalCenter width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale Text { - text: "Create a Studio" + text: "Create Studio" font.family: "Poppins" font.pixelSize: 11 * virtualstudio.fontScale * virtualstudio.uiScale font.weight: Font.Bold - color: "#DB0A0A" + color: "#000000" anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter } @@ -92,7 +92,6 @@ Rectangle { 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 { @@ -127,7 +126,6 @@ Rectangle { 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 { diff --git a/src/gui/Settings.qml b/src/vs/Settings.qml similarity index 85% rename from src/gui/Settings.qml rename to src/vs/Settings.qml index b970e91..e969637 100644 --- a/src/gui/Settings.qml +++ b/src/vs/Settings.qml @@ -59,6 +59,13 @@ Item { return idx; } + function getQueueBufferString () { + if (audio.queueBuffer == 0) { + return "auto"; + } + return audio.queueBuffer + " ms"; + } + Rectangle { id: audioSettingsView width: 0.8 * parent.width @@ -340,8 +347,10 @@ Item { border.color: darkButton.down ? buttonPressedStroke : (darkButton.hovered ? buttonHoverStroke : buttonStroke) } onClicked: { virtualstudio.darkMode = !virtualstudio.darkMode; } - x: parent.width - (232 * virtualstudio.uiScale); y: modeButton.y + (56 * virtualstudio.uiScale) - width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale + x: parent.width - (232 * virtualstudio.uiScale); + y: scaleSlider.y + (48 * virtualstudio.uiScale) + width: 216 * virtualstudio.uiScale; + height: 30 * virtualstudio.uiScale Text { text: virtualstudio.darkMode ? "Switch to Light Mode" : "Switch to Dark Mode" font { family: "Poppins"; pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale } @@ -370,6 +379,7 @@ Item { Button { id: modeButton + visible: virtualstudio.hasClassicMode background: Rectangle { radius: 6 * virtualstudio.uiScale color: modeButton.down ? buttonPressedColour : (modeButton.hovered ? buttonHoverColour : buttonColour) @@ -384,10 +394,10 @@ Item { audio.validateDevices(); // switch mode - virtualstudio.toStandard(); + virtualstudio.toClassicMode(); } x: 220 * virtualstudio.uiScale; - y: 100 * virtualstudio.uiScale + y: 48 * virtualstudio.uiScale width: 216 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale Text { @@ -399,6 +409,7 @@ Item { } Text { + visible: virtualstudio.hasClassicMode anchors.verticalCenter: modeButton.verticalCenter x: leftMargin * virtualstudio.uiScale text: "Display Mode" @@ -408,7 +419,8 @@ Item { ComboBox { id: updateChannelCombo - x: 220 * virtualstudio.uiScale; y: modeButton.y + (48 * virtualstudio.uiScale) + x: 220 * virtualstudio.uiScale; + y: virtualstudio.hasClassicMode ? (modeButton.y + (48 * virtualstudio.uiScale)) : (48 * virtualstudio.uiScale) width: parent.width - x - (16 * virtualstudio.uiScale); height: 36 * virtualstudio.uiScale model: virtualstudio.updateChannelComboModel currentIndex: virtualstudio.updateChannel == "stable" ? 0 : 1 @@ -468,44 +480,117 @@ Item { color: textColour } - ComboBox { - id: bufferStrategyCombo - x: updateChannelCombo.x; y: bufferCombo.y + (48 * virtualstudio.uiScale) - width: updateChannelCombo.width; height: updateChannelCombo.height - model: audio.bufferStrategyComboModel - currentIndex: audio.bufferStrategy - onActivated: { audio.bufferStrategy = currentIndex } - font.family: "Poppins" + Slider { + id: queueBufferSlider + value: audio.queueBuffer + onMoved: { + audio.queueBuffer = value; + } + from: 0 + to: 128 + stepSize: 1 + padding: 0 + x: updateChannelCombo.x + queueBufferText.width + y: bufferCombo.y + (54 * virtualstudio.uiScale) + width: updateChannelCombo.width - queueBufferText.width + + background: Rectangle { + x: queueBufferSlider.leftPadding + y: queueBufferSlider.topPadding + queueBufferSlider.availableHeight / 2 - height / 2 + implicitWidth: parent.width + implicitHeight: 6 + width: queueBufferSlider.availableWidth + height: implicitHeight + radius: 4 + color: sliderTrackColour + + Rectangle { + width: queueBufferSlider.visualPosition * parent.width + height: parent.height + color: sliderActiveTrackColour + radius: 4 + } + } + + handle: Rectangle { + x: queueBufferSlider.leftPadding + queueBufferSlider.visualPosition * (queueBufferSlider.availableWidth - width) + y: queueBufferSlider.topPadding + queueBufferSlider.availableHeight / 2 - height / 2 + implicitWidth: 26 * virtualstudio.uiScale + implicitHeight: 26 * virtualstudio.uiScale + radius: 13 * virtualstudio.uiScale + color: queueBufferSlider.pressed ? sliderPressedColour : sliderColour + border.color: buttonStroke + } } Text { - anchors.verticalCenter: bufferStrategyCombo.verticalCenter - x: 48 * virtualstudio.uiScale - text: "Buffer Strategy" + id: queueBufferText + width: (64 * virtualstudio.uiScale) + x: updateChannelCombo.x; + anchors.verticalCenter: queueBufferSlider.verticalCenter + text: getQueueBufferString() font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } color: textColour } - ComboBox { - id: feedbackDetectionCombo - x: updateChannelCombo.x; y: bufferStrategyCombo.y + (48 * virtualstudio.uiScale) - width: updateChannelCombo.width; height: updateChannelCombo.height - model: audio.feedbackDetectionComboModel - currentIndex: audio.feedbackDetectionEnabled ? 0 : 1 - onActivated: { - if (currentIndex === 1) { - audio.feedbackDetectionEnabled = false; - } else { - audio.feedbackDetectionEnabled = true; + Text { + id: queueBufferLabel + anchors.verticalCenter: queueBufferSlider.verticalCenter + x: leftMargin * virtualstudio.uiScale + text: "Adjust Latency" + font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } + color: textColour + } + + InfoTooltip { + id: tooltip + content: "JackTrip analyzes your Internet connection to find the best balance between audio latency and quality. Add additional latency to further improve quality." + size: 16 + anchors.left: queueBufferLabel.right + anchors.leftMargin: 2 * virtualstudio.uiScale + anchors.verticalCenter: queueBufferSlider.verticalCenter + } + + CheckBox { + id: feedbackDetection + checked: audio.feedbackDetectionEnabled + text: qsTr("Automatically mute when feedback is detected") + x: updateChannelCombo.x; + y: queueBufferSlider.y + (48 * virtualstudio.uiScale) + onClicked: { audio.feedbackDetectionEnabled = feedbackDetection.checkState == Qt.Checked; } + indicator: Rectangle { + implicitWidth: 16 * virtualstudio.uiScale + implicitHeight: 16 * virtualstudio.uiScale + x: feedbackDetection.leftPadding + y: parent.height / 2 - height / 2 + radius: 3 * virtualstudio.uiScale + border.color: feedbackDetection.down || feedbackDetection.hovered ? checkboxPressedStroke : checkboxStroke + + Rectangle { + width: 10 * virtualstudio.uiScale + height: 10 * virtualstudio.uiScale + x: 3 * virtualstudio.uiScale + y: 3 * virtualstudio.uiScale + radius: 2 * virtualstudio.uiScale + color: feedbackDetection.down || feedbackDetection.hovered ? checkboxPressedStroke : checkboxStroke + visible: feedbackDetection.checked } } - font.family: "Poppins" + contentItem: Text { + text: feedbackDetection.text + font.family: "Poppins" + font.pixelSize: 10 * virtualstudio.fontScale * virtualstudio.uiScale + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + leftPadding: feedbackDetection.indicator.width + feedbackDetection.spacing + color: textColour + } } Text { - anchors.verticalCenter: feedbackDetectionCombo.verticalCenter + anchors.verticalCenter: feedbackDetection.verticalCenter x: 48 * virtualstudio.uiScale - text: "Feedback Detection" + text: "Detect Feedback" font { family: "Poppins"; pixelSize: fontMedium * virtualstudio.fontScale * virtualstudio.uiScale } color: textColour } @@ -514,7 +599,7 @@ Item { id: showStartupSetup checked: virtualstudio.showDeviceSetup text: qsTr("Show device setup screen before connecting to a studio") - x: updateChannelCombo.x; y: feedbackDetectionCombo.y + (48 * virtualstudio.uiScale) + x: updateChannelCombo.x; y: feedbackDetection.y + (48 * virtualstudio.uiScale) onClicked: { virtualstudio.showDeviceSetup = showStartupSetup.checkState == Qt.Checked; } indicator: Rectangle { implicitWidth: 16 * virtualstudio.uiScale @@ -661,7 +746,7 @@ Item { border.width: 1 border.color: logoutButton.down ? buttonPressedStroke : (logoutButton.hovered ? buttonHoverStroke : buttonStroke) } - onClicked: { virtualstudio.showFirstRun = false; virtualstudio.logout(); } + onClicked: { virtualstudio.logout(); } anchors.horizontalCenter: parent.horizontalCenter y: editButton.y + (48 * virtualstudio.uiScale) width: 260 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale diff --git a/src/gui/Setup.qml b/src/vs/Setup.qml similarity index 92% rename from src/gui/Setup.qml rename to src/vs/Setup.qml index 969325d..a28a8dd 100644 --- a/src/gui/Setup.qml +++ b/src/vs/Setup.qml @@ -25,7 +25,7 @@ Item { property string saveButtonPressedColour: "#E7E8E8" property string saveButtonStroke: "#EAEBEB" property string saveButtonPressedStroke: "#B0B5B5" - property string saveButtonText: "#DB0A0A" + property string saveButtonText: "#000000" property string checkboxStroke: "#0062cc" property string checkboxPressedStroke: "#007AFF" property string disabledButtonText: virtualstudio.darkMode ? "#827D7D" : "#BABCBC" @@ -136,19 +136,23 @@ Item { } enabled: !Boolean(audio.devicesError) && audio.backendAvailable && audio.audioReady onClicked: { - audio.stopAudio(true); - virtualstudio.studioToJoin = virtualstudio.currentStudio.id; - virtualstudio.windowState = "connected"; - virtualstudio.saveSettings(); - virtualstudio.joinStudio(); + if (Boolean(audio.devicesWarning)) { + deviceWarningModal.open(); + } else { + audio.stopAudio(true); + virtualstudio.studioToJoin = virtualstudio.currentStudio.id; + virtualstudio.windowState = "connected"; + virtualstudio.saveSettings(); + virtualstudio.joinStudio(); + } } anchors.right: parent.right anchors.rightMargin: rightMargin * virtualstudio.uiScale anchors.bottomMargin: rightMargin * virtualstudio.uiScale anchors.bottom: parent.bottom - width: 150 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale + width: 160 * virtualstudio.uiScale; height: 30 * virtualstudio.uiScale Text { - text: "Connect to Studio" + text: "Connect to Session" font.family: "Poppins" font.pixelSize: fontSmall * virtualstudio.fontScale * virtualstudio.uiScale font.weight: Font.Bold @@ -197,5 +201,9 @@ Item { } } } + + DeviceWarningModal { + id: deviceWarningModal + } } } diff --git a/src/gui/Studio.qml b/src/vs/Studio.qml similarity index 100% rename from src/gui/Studio.qml rename to src/vs/Studio.qml diff --git a/src/gui/VolumeSlider.qml b/src/vs/VolumeSlider.qml similarity index 100% rename from src/gui/VolumeSlider.qml rename to src/vs/VolumeSlider.qml diff --git a/src/gui/Web.qml b/src/vs/Web.qml similarity index 100% rename from src/gui/Web.qml rename to src/vs/Web.qml diff --git a/src/gui/WebEngine.qml b/src/vs/WebEngine.qml similarity index 100% rename from src/gui/WebEngine.qml rename to src/vs/WebEngine.qml diff --git a/src/gui/WebNull.qml b/src/vs/WebNull.qml similarity index 100% rename from src/gui/WebNull.qml rename to src/vs/WebNull.qml diff --git a/src/gui/WebSocketTransport.cpp b/src/vs/WebSocketTransport.cpp similarity index 100% rename from src/gui/WebSocketTransport.cpp rename to src/vs/WebSocketTransport.cpp diff --git a/src/gui/WebSocketTransport.h b/src/vs/WebSocketTransport.h similarity index 100% rename from src/gui/WebSocketTransport.h rename to src/vs/WebSocketTransport.h diff --git a/src/gui/WebView.qml b/src/vs/WebView.qml similarity index 100% rename from src/gui/WebView.qml rename to src/vs/WebView.qml diff --git a/src/gui/check.svg b/src/vs/check.svg similarity index 100% rename from src/gui/check.svg rename to src/vs/check.svg diff --git a/src/gui/close.svg b/src/vs/close.svg similarity index 100% rename from src/gui/close.svg rename to src/vs/close.svg diff --git a/src/gui/cog.svg b/src/vs/cog.svg similarity index 100% rename from src/gui/cog.svg rename to src/vs/cog.svg diff --git a/src/gui/ethernet.svg b/src/vs/ethernet.svg similarity index 100% rename from src/gui/ethernet.svg rename to src/vs/ethernet.svg diff --git a/src/gui/expand_less.svg b/src/vs/expand_less.svg similarity index 100% rename from src/gui/expand_less.svg rename to src/vs/expand_less.svg diff --git a/src/gui/expand_more.svg b/src/vs/expand_more.svg similarity index 100% rename from src/gui/expand_more.svg rename to src/vs/expand_more.svg diff --git a/src/gui/externalMic.svg b/src/vs/externalMic.svg similarity index 100% rename from src/gui/externalMic.svg rename to src/vs/externalMic.svg diff --git a/src/gui/flags/AE.svg b/src/vs/flags/AE.svg similarity index 100% rename from src/gui/flags/AE.svg rename to src/vs/flags/AE.svg diff --git a/src/gui/flags/AU.svg b/src/vs/flags/AU.svg similarity index 100% rename from src/gui/flags/AU.svg rename to src/vs/flags/AU.svg diff --git a/src/gui/flags/BE.svg b/src/vs/flags/BE.svg similarity index 100% rename from src/gui/flags/BE.svg rename to src/vs/flags/BE.svg diff --git a/src/gui/flags/BR.svg b/src/vs/flags/BR.svg similarity index 100% rename from src/gui/flags/BR.svg rename to src/vs/flags/BR.svg diff --git a/src/gui/flags/CA.svg b/src/vs/flags/CA.svg similarity index 100% rename from src/gui/flags/CA.svg rename to src/vs/flags/CA.svg diff --git a/src/gui/flags/CH.svg b/src/vs/flags/CH.svg similarity index 100% rename from src/gui/flags/CH.svg rename to src/vs/flags/CH.svg diff --git a/src/gui/flags/DE.svg b/src/vs/flags/DE.svg similarity index 100% rename from src/gui/flags/DE.svg rename to src/vs/flags/DE.svg diff --git a/src/gui/flags/FR.svg b/src/vs/flags/FR.svg similarity index 100% rename from src/gui/flags/FR.svg rename to src/vs/flags/FR.svg diff --git a/src/gui/flags/GB.svg b/src/vs/flags/GB.svg similarity index 100% rename from src/gui/flags/GB.svg rename to src/vs/flags/GB.svg diff --git a/src/gui/flags/HK.svg b/src/vs/flags/HK.svg similarity index 100% rename from src/gui/flags/HK.svg rename to src/vs/flags/HK.svg diff --git a/src/gui/flags/ID.svg b/src/vs/flags/ID.svg similarity index 100% rename from src/gui/flags/ID.svg rename to src/vs/flags/ID.svg diff --git a/src/gui/flags/IT.svg b/src/vs/flags/IT.svg similarity index 100% rename from src/gui/flags/IT.svg rename to src/vs/flags/IT.svg diff --git a/src/gui/flags/JP.svg b/src/vs/flags/JP.svg similarity index 100% rename from src/gui/flags/JP.svg rename to src/vs/flags/JP.svg diff --git a/src/gui/flags/RO.svg b/src/vs/flags/RO.svg similarity index 100% rename from src/gui/flags/RO.svg rename to src/vs/flags/RO.svg diff --git a/src/gui/flags/SE.svg b/src/vs/flags/SE.svg similarity index 100% rename from src/gui/flags/SE.svg rename to src/vs/flags/SE.svg diff --git a/src/gui/flags/SG.svg b/src/vs/flags/SG.svg similarity index 100% rename from src/gui/flags/SG.svg rename to src/vs/flags/SG.svg diff --git a/src/gui/flags/TW.svg b/src/vs/flags/TW.svg similarity index 100% rename from src/gui/flags/TW.svg rename to src/vs/flags/TW.svg diff --git a/src/gui/flags/US.svg b/src/vs/flags/US.svg similarity index 100% rename from src/gui/flags/US.svg rename to src/vs/flags/US.svg diff --git a/src/gui/flags/ZA.svg b/src/vs/flags/ZA.svg similarity index 100% rename from src/gui/flags/ZA.svg rename to src/vs/flags/ZA.svg diff --git a/src/gui/headphones.svg b/src/vs/headphones.svg similarity index 100% rename from src/gui/headphones.svg rename to src/vs/headphones.svg diff --git a/src/gui/help.svg b/src/vs/help.svg similarity index 100% rename from src/gui/help.svg rename to src/vs/help.svg diff --git a/src/gui/jacktrip white.png b/src/vs/jacktrip white.png similarity index 100% rename from src/gui/jacktrip white.png rename to src/vs/jacktrip white.png diff --git a/src/gui/jacktrip.png b/src/vs/jacktrip.png similarity index 100% rename from src/gui/jacktrip.png rename to src/vs/jacktrip.png diff --git a/src/gui/join.svg b/src/vs/join.svg similarity index 100% rename from src/gui/join.svg rename to src/vs/join.svg diff --git a/src/gui/language.svg b/src/vs/language.svg similarity index 100% rename from src/gui/language.svg rename to src/vs/language.svg diff --git a/src/gui/leave.svg b/src/vs/leave.svg similarity index 100% rename from src/gui/leave.svg rename to src/vs/leave.svg diff --git a/src/gui/logo.svg b/src/vs/logo.svg similarity index 100% rename from src/gui/logo.svg rename to src/vs/logo.svg diff --git a/src/gui/loud.svg b/src/vs/loud.svg similarity index 100% rename from src/gui/loud.svg rename to src/vs/loud.svg diff --git a/src/gui/manage.svg b/src/vs/manage.svg similarity index 100% rename from src/gui/manage.svg rename to src/vs/manage.svg diff --git a/src/gui/mic.svg b/src/vs/mic.svg similarity index 100% rename from src/gui/mic.svg rename to src/vs/mic.svg diff --git a/src/gui/micoff.svg b/src/vs/micoff.svg similarity index 100% rename from src/gui/micoff.svg rename to src/vs/micoff.svg diff --git a/src/gui/network.svg b/src/vs/network.svg similarity index 100% rename from src/gui/network.svg rename to src/vs/network.svg diff --git a/src/gui/networkCheck.svg b/src/vs/networkCheck.svg similarity index 100% rename from src/gui/networkCheck.svg rename to src/vs/networkCheck.svg diff --git a/src/gui/private.svg b/src/vs/private.svg similarity index 100% rename from src/gui/private.svg rename to src/vs/private.svg diff --git a/src/gui/public.svg b/src/vs/public.svg similarity index 100% rename from src/gui/public.svg rename to src/vs/public.svg diff --git a/src/gui/quiet.svg b/src/vs/quiet.svg similarity index 100% rename from src/gui/quiet.svg rename to src/vs/quiet.svg diff --git a/src/gui/refresh.svg b/src/vs/refresh.svg similarity index 100% rename from src/gui/refresh.svg rename to src/vs/refresh.svg diff --git a/src/gui/sentiment_very_dissatisfied.svg b/src/vs/sentiment_very_dissatisfied.svg similarity index 100% rename from src/gui/sentiment_very_dissatisfied.svg rename to src/vs/sentiment_very_dissatisfied.svg diff --git a/src/gui/share.svg b/src/vs/share.svg similarity index 100% rename from src/gui/share.svg rename to src/vs/share.svg diff --git a/src/gui/speed.svg b/src/vs/speed.svg similarity index 100% rename from src/gui/speed.svg rename to src/vs/speed.svg diff --git a/src/gui/star.svg b/src/vs/star.svg similarity index 100% rename from src/gui/star.svg rename to src/vs/star.svg diff --git a/src/gui/start.svg b/src/vs/start.svg similarity index 100% rename from src/gui/start.svg rename to src/vs/start.svg diff --git a/src/gui/video.svg b/src/vs/video.svg similarity index 100% rename from src/gui/video.svg rename to src/vs/video.svg diff --git a/src/gui/virtualstudio.cpp b/src/vs/virtualstudio.cpp similarity index 72% rename from src/gui/virtualstudio.cpp rename to src/vs/virtualstudio.cpp index 7223a99..6f27a67 100644 --- a/src/gui/virtualstudio.cpp +++ b/src/vs/virtualstudio.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -37,14 +36,25 @@ #include "virtualstudio.h" +#include + #include #include +#include +#include +#include #include #include #include +#include +#include +#include #include #include +#include #include +#include +#include #include #include @@ -55,30 +65,46 @@ #include "../JackTrip.h" #include "../Settings.h" #include "../jacktrip_globals.h" +#include "JTApplication.h" #include "WebSocketTransport.h" -#include "about.h" -#include "qjacktrip.h" #include "vsApi.h" #include "vsAudio.h" #include "vsAuth.h" +#include "vsDeeplink.h" #include "vsDevice.h" +#include "vsQmlClipboard.h" #include "vsWebSocket.h" -#ifdef __APPLE__ -#include "vsMacPermissions.h" -#else -#include "vsPermissions.h" -#endif - #ifdef _WIN32 #include #endif -VirtualStudio::VirtualStudio(bool firstRun, QObject* parent) - : QObject(parent) +#if defined(JACKTRIP_BUILD_INFO) +#define STR(s) #s +#define TO_STRING(s) STR(s) +const QString jackTripBuildInfo = QLatin1String(TO_STRING(JACKTRIP_BUILD_INFO)); +#else +const QString jackTripBuildInfo = QLatin1String(""); +#endif + +static QTextStream* ts; +static QFile outFile; + +void qtMessageHandler([[maybe_unused]] QtMsgType type, + [[maybe_unused]] const QMessageLogContext& context, + const QString& msg) +{ + std::cerr << msg.toStdString() << std::endl; + // Writes to file in order to debug bundles and executables + *ts << msg << Qt::endl; +} + +VirtualStudio::VirtualStudio(UserInterface& parent) + : QObject() + , m_interface(parent) + , m_view(new VsQuickView) , m_audioConfigPtr( new VsAudio(this)) // this needs to be constructed before loadSettings() - , m_showFirstRun(firstRun) { // load or initialize persisted settings loadSettings(); @@ -142,34 +168,31 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent) emit updatedNetworkOutage(false); }); - if ((m_uiMode == QJackTrip::UNSET && vsFtux()) - || (m_uiMode == QJackTrip::VIRTUAL_STUDIO)) { - m_windowState = QStringLiteral("login"); - } - // register QML types qmlRegisterType("org.jacktrip.jacktrip", 1, 0, "VsServerInfo"); + // Register clipboard Qml type + qmlRegisterType("VS", 1, 0, "Clipboard"); + // setup QML view - m_view.engine()->rootContext()->setContextProperty(QStringLiteral("virtualstudio"), - this); - m_view.engine()->rootContext()->setContextProperty(QStringLiteral("auth"), - m_auth.get()); - m_view.engine()->rootContext()->setContextProperty(QStringLiteral("audio"), - m_audioConfigPtr.get()); - m_view.engine()->rootContext()->setContextProperty( + m_view->engine()->rootContext()->setContextProperty(QStringLiteral("virtualstudio"), + this); + m_view->engine()->rootContext()->setContextProperty(QStringLiteral("auth"), + m_auth.get()); + m_view->engine()->rootContext()->setContextProperty(QStringLiteral("audio"), + m_audioConfigPtr.get()); + m_view->engine()->rootContext()->setContextProperty( QStringLiteral("permissions"), QVariant::fromValue(&m_audioConfigPtr->getPermissions())); - m_view.setSource(QUrl(QStringLiteral("qrc:/vs/vs.qml"))); - m_view.setMinimumSize(QSize(800, 640)); - // m_view.setMaximumSize(QSize(696, 577)); - m_view.setResizeMode(QQuickView::SizeRootObjectToView); - m_view.resize(800 * m_uiScale, 640 * m_uiScale); + m_view->setSource(QUrl(QStringLiteral("qrc:/vs/vs.qml"))); + m_view->setMinimumSize(QSize(800, 640)); + // m_view->setMaximumSize(QSize(696, 577)); + m_view->setResizeMode(QQuickView::SizeRootObjectToView); + m_view->resize(800 * m_uiScale, 640 * m_uiScale); // Connect our timers - connect(&m_refreshTimer, &QTimer::timeout, this, [&]() { - emit periodicRefresh(); - }); + connect(this, &VirtualStudio::scheduleStudioRefresh, this, + &VirtualStudio::refreshStudios, Qt::QueuedConnection); connect(&m_heartbeatTimer, &QTimer::timeout, this, &VirtualStudio::sendHeartbeat, Qt::QueuedConnection); @@ -187,23 +210,36 @@ VirtualStudio::VirtualStudio(bool firstRun, QObject* parent) &VirtualStudio::detectedFeedbackLoop, Qt::QueuedConnection); // call exit() when the UI window is closed - connect(&m_view, &VsQuickView::windowClose, this, &VirtualStudio::exit, + connect(m_view.get(), &VsQuickView::windowClose, this, &VirtualStudio::exit, Qt::QueuedConnection); - if ((m_uiMode == QJackTrip::UNSET && vsFtux()) - || (m_uiMode == QJackTrip::VIRTUAL_STUDIO)) { - login(); + // prepare handler for deeplinks jacktrip://join/ + m_deepLinkPtr.reset(new VsDeeplink(m_interface.getSettings().getDeeplink())); + if (!m_deepLinkPtr->getDeeplink().isEmpty()) { + bool readyForExit = m_deepLinkPtr->waitForReady(); + if (readyForExit) + std::exit(0); } -} -void VirtualStudio::setStandardWindow(QSharedPointer window) -{ - m_standardWindow = window; -} + // Log to file + QString logPath(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); + QDir logDir; + if (!logDir.exists(logPath)) { + logDir.mkpath(logPath); + } + QString fileLoc(logPath.append("/log.txt")); + qDebug() << "Log file location:" << fileLoc; + outFile.setFileName(fileLoc); + if (!outFile.open(QIODevice::WriteOnly | QIODevice::Append)) { + qDebug() << "Log file open failed:" << outFile.errorString(); + } + ts = new QTextStream(&outFile); + qInstallMessageHandler(qtMessageHandler); -void VirtualStudio::setCLISettings(QSharedPointer settings) -{ - m_cliSettings = settings; + // Get ready for deep link signals + QObject::connect(m_deepLinkPtr.get(), &VsDeeplink::signalDeeplink, this, + &VirtualStudio::handleDeeplinkRequest, Qt::QueuedConnection); + m_deepLinkPtr->readyForSignals(); } void VirtualStudio::show() @@ -226,14 +262,14 @@ void VirtualStudio::show() m_checkSsl = false; } - while (m_view.status() == QQuickView::Loading) { + while (m_view->status() == QQuickView::Loading) { // I don't think there is any need to load network data, but just in case // See https://doc.qt.io/qt-6/qquickview.html#Status-enum qDebug() << "JackTrip is still loading the QML view"; QThread::sleep(1); } - if (m_view.status() != QQuickView::Ready) { + if (m_view->status() != QQuickView::Ready) { QMessageBox msgBox; msgBox.setText( "JackTrip detected that some modules required for the " @@ -243,7 +279,7 @@ void VirtualStudio::show() "This is likely caused by missing QML plugins. " "Please consult support.jacktrip.com for possible solutions."); msgBox.setWindowTitle(QStringLiteral("JackTrip Is Missing QML Modules")); - connect(&msgBox, &QMessageBox::finished, this, &VirtualStudio::toStandard, + connect(&msgBox, &QMessageBox::finished, this, &VirtualStudio::toClassicMode, Qt::QueuedConnection); msgBox.exec(); return; @@ -252,13 +288,19 @@ void VirtualStudio::show() raiseToTop(); } +void VirtualStudio::hide() +{ + if (!m_view.isNull()) + m_view->hide(); +} + void VirtualStudio::raiseToTop() { - if (m_view.status() != QQuickView::Ready) + if (m_view->status() != QQuickView::Ready) return; - m_view.show(); // Restore from systray - m_view.raise(); // raise to top - m_view.requestActivate(); // focus on window + m_view->show(); // Restore from systray + m_view->raise(); // raise to top + m_view->requestActivate(); // focus on window } int VirtualStudio::webChannelPort() @@ -266,27 +308,69 @@ int VirtualStudio::webChannelPort() return m_webChannelPort; } -bool VirtualStudio::showFirstRun() +bool VirtualStudio::hasRefreshToken() { - return m_showFirstRun; + return !m_refreshToken.isEmpty(); } -void VirtualStudio::setShowFirstRun(bool show) +QString VirtualStudio::versionString() { - if (m_showFirstRun == show) - return; - m_showFirstRun = show; - emit showFirstRunChanged(); + return QLatin1String(gVersion); } -bool VirtualStudio::hasRefreshToken() +QString VirtualStudio::buildString() { - return !m_refreshToken.isEmpty(); + QString result; + if (!jackTripBuildInfo.isEmpty()) { + result += "Build "; + result += jackTripBuildInfo; + result += "
\n"; + } + result += "Qt version "; + result += QT_VERSION_STR; +#ifdef QT_OPENSOURCE + result += " (open source)"; +#else + result += " (commercial)"; +#endif + return result; } -QString VirtualStudio::versionString() +QString VirtualStudio::copyrightString() { - return QLatin1String(gVersion); + QString result; + bool gplLicense = false; +#ifdef QT_OPENSOURCE + gplLicense = true; +#endif + + result += + "Copyright © 2008-2024 Juan-Pablo Caceres, Chris Chafe, et al. SoundWIRE " + "group at CCRMA, Stanford University.

\n"; + result += + "Virtual Studio interface and integration Copyright © 2022-2024 JackTrip " + "Labs, Inc.

\n"; + + if (hasClassicMode()) { + gplLicense = true; + result += + "Classic mode graphical user interface component originally released as " + "QJackTrip, Copyright © 2020 Aaron Wyatt.

\n"; + } + + if (m_audioConfigPtr->asioIsAvailable()) { + result += + "This build of JackTrip includes support for ASIO. ASIO is a trademark and " + "software of Steinberg Media Technologies GmbH.

"; + } + + result += + "This app is free and open source software provided "as is" under the "; + result += (gplLicense ? "GPL" : "MIT"); + result += " license, without warranty of any kind.\n"; + result += "See the included LICENSE.md file for more information.

\n"; + + return result; } QString VirtualStudio::logoSection() @@ -381,19 +465,6 @@ void VirtualStudio::setShowSelfHosted(bool selfHosted) settings.endGroup(); } -bool VirtualStudio::showCreateStudio() -{ - return m_showCreateStudio; -} - -void VirtualStudio::setShowCreateStudio(bool createStudio) -{ - if (m_showCreateStudio == createStudio) - return; - m_showCreateStudio = createStudio; - emit showCreateStudioChanged(); -} - bool VirtualStudio::showDeviceSetup() { return m_showDeviceSetup; @@ -417,6 +488,13 @@ void VirtualStudio::setWindowState(QString state) if (m_windowState == state) return; m_windowState = state; + // refresh studio list if navigating to browse window + // only if user id is empty (edge case for when logging in) + if (m_windowState == "browse" && !m_userId.isEmpty()) { + // schedule studio refresh instead of doing it now + // just to reduce risk of running into a deadlock + emit scheduleStudioRefresh(-1, false); + } emit windowStateUpdated(); } @@ -443,6 +521,19 @@ bool VirtualStudio::isExiting() return m_isExiting; } +bool VirtualStudio::refreshInProgress() +{ + return m_refreshInProgress; +} + +void VirtualStudio::setRefreshInProgress(bool b) +{ + if (m_refreshInProgress == b) + return; + m_refreshInProgress = b; + emit refreshInProgressChanged(); +} + void VirtualStudio::collectFeedbackSurvey(QString serverId, int rating, QString message) { QJsonObject feedback; @@ -629,10 +720,10 @@ void VirtualStudio::joinStudio() QMutexLocker locker(&m_refreshMutex); if (m_servers.isEmpty()) { // No servers yet. Making sure we have them. - // getServerList emits refreshFinished which + // refreshStudios emits refreshFinished which // will come back to this function. locker.unlock(); - getServerList(true); + refreshStudios(-1, true); return; } @@ -673,40 +764,37 @@ void VirtualStudio::joinStudio() connectToStudio(); } -void VirtualStudio::toStandard() +bool VirtualStudio::hasClassicMode() { - if (m_standardWindow.isNull()) - qDebug() << "Unable to switch modes: standard window is missing!"; - - m_view.hide(); - m_standardWindow->show(); +#ifdef NO_CLASSIC + return false; +#else + return true; +#endif // NO_CLASSIC +} +void VirtualStudio::toVirtualStudioMode() +{ QSettings settings; - m_uiMode = QJackTrip::STANDARD; - settings.setValue(QStringLiteral("UiMode"), m_uiMode); - - // stop timers, clear data, etc. - resetState(); - setWindowState(QStringLiteral("start")); - m_auth->logout(); - - if (m_showFirstRun) { - m_showFirstRun = false; - emit showFirstRunChanged(); - } + settings.setValue(QStringLiteral("UiMode"), UserInterface::MODE_VS); + setWindowState(QStringLiteral("login")); + login(); } -void VirtualStudio::toVirtualStudio() +void VirtualStudio::toClassicMode() { - QSettings settings; - m_uiMode = QJackTrip::VIRTUAL_STUDIO; - settings.setValue(QStringLiteral("UiMode"), m_uiMode); + if (hasClassicMode()) { + m_interface.setMode(UserInterface::MODE_CLASSIC); - if (m_windowState == "start") { - setWindowState(QStringLiteral("login")); - } - if (m_windowState == "login") { - login(); + QSettings settings; + settings.setValue(QStringLiteral("UiMode"), UserInterface::MODE_CLASSIC); + + // stop timers, clear data, etc. + resetState(); + setWindowState(QStringLiteral("start")); + m_auth->logout(); + } else { + std::cerr << "JackTrip was not built with support for Classic mode." << std::endl; } } @@ -764,17 +852,9 @@ void VirtualStudio::logout() login(); // called to retrieve new code flow token } -void VirtualStudio::refreshStudios(int index, bool signalRefresh) -{ - getSubscriptions(); - getServerList(signalRefresh, index); -} - void VirtualStudio::loadSettings() { QSettings settings; - m_uiMode = static_cast( - settings.value(QStringLiteral("UiMode"), QJackTrip::UNSET).toInt()); setUpdateChannel( settings.value(QStringLiteral("UpdateChannel"), "stable").toString().toLower()); @@ -783,7 +863,7 @@ void VirtualStudio::loadSettings() m_userId = settings.value(QStringLiteral("UserId"), "").toString(); m_testMode = settings.value(QStringLiteral("TestMode"), false).toBool(); m_showInactive = settings.value(QStringLiteral("ShowInactive"), true).toBool(); - m_showSelfHosted = settings.value(QStringLiteral("ShowSelfHosted"), false).toBool(); + m_showSelfHosted = settings.value(QStringLiteral("ShowSelfHosted"), true).toBool(); // use setters to emit signals for these if they change; otherwise, the // user interface will not revert back after cancelling settings changes @@ -812,8 +892,6 @@ void VirtualStudio::saveSettings() void VirtualStudio::connectToStudio() { - m_refreshTimer.stop(); - m_networkStats = QJsonObject(); emit networkStatsChanged(); @@ -892,23 +970,24 @@ void VirtualStudio::completeConnection() } #endif - // increment buffer_strategy by 1 for array-index mapping - int buffer_strategy = m_audioConfigPtr->getBufferStrategy() + 1; - // adjust buffer_strategy for PLC "auto" mode menu item - if (buffer_strategy == 4 || buffer_strategy == 5) { - buffer_strategy = 3; + // adjust queueBuffer config setting to map to auto headroom + int queue_buffer = m_audioConfigPtr->getQueueBuffer(); + if (queue_buffer <= 0) { + queue_buffer = -500; + } else { + queue_buffer *= -1; } // create a new JackTrip instance JackTrip* jackTrip = m_devicePtr->initJackTrip( useRtAudio, input, output, baseInputChannel, numInputChannels, - baseOutputChannel, numOutputChannels, inputMixMode, buffer_size, - buffer_strategy, &m_currentStudio); + baseOutputChannel, numOutputChannels, inputMixMode, buffer_size, queue_buffer, + &m_currentStudio); if (jackTrip == 0) { processError("Could not bind port"); return; } - jackTrip->setIOStatTimeout(m_cliSettings->getIOStatTimeout()); + jackTrip->setIOStatTimeout(m_interface.getSettings().getIOStatTimeout()); m_audioConfigPtr->setSampleRate(jackTrip->getSampleRate()); // this passes ownership to JackTrip @@ -946,9 +1025,7 @@ void VirtualStudio::completeConnection() return; } -#ifdef __APPLE__ - m_noNap.disableNap(); -#endif + m_interface.enableNap(); } void VirtualStudio::triggerReconnect(bool refresh) @@ -998,9 +1075,6 @@ void VirtualStudio::disconnect() if (m_onConnectedScreen) { m_onConnectedScreen = false; emit disconnected(); - // Refresh studios and restart timer - refreshStudios(0, true); - m_refreshTimer.start(); } if (!m_jackTripRunning) { @@ -1023,26 +1097,21 @@ void VirtualStudio::disconnect() emit currentStudioChanged(); } -#ifdef __APPLE__ - m_noNap.enableNap(); -#endif -} - -void VirtualStudio::createStudio() -{ - setWindowState(QStringLiteral("create_studio")); + m_interface.enableNap(); } void VirtualStudio::editProfile() { - QUrl url = QUrl(QStringLiteral("https://%1/profile").arg(m_api->getApiHost())); + QUrl url = QUrl(QStringLiteral("https://www.jacktrip.com/profile")); + if (testMode()) { + url = QUrl(QStringLiteral("https://next-test.jacktrip.com/profile")); + } QDesktopServices::openUrl(url); } void VirtualStudio::showAbout() { - About about; - about.exec(); + emit openAboutWindow(); } void VirtualStudio::openLink(const QString& link) @@ -1082,18 +1151,13 @@ void VirtualStudio::handleDeeplinkRequest(const QUrl& link) qDebug() << "Handling deeplink to" << link; setStudioToJoin(studioId); - raiseToTop(); // Switch to virtual studio mode, if necessary // Note that this doesn't change the startup preference - if (m_uiMode != QJackTrip::VIRTUAL_STUDIO) { - m_standardWindow->hide(); - if (m_windowState == "start") { - setWindowState(QStringLiteral("login")); - } - if (m_windowState == "login") { - login(); - } + if (m_interface.getMode() != UserInterface::MODE_VS) { + m_interface.setMode(UserInterface::MODE_VS); + } else { + raiseToTop(); } // automatically navigate if on certain window screens @@ -1189,9 +1253,7 @@ void VirtualStudio::slotAuthSucceeded() std::cout << "QWebChannel listening on port: " << m_webChannelPort << std::endl; getRegions(); - getUserMetadata(); - getSubscriptions(); - getServerList(false); + getUserMetadata(); // calls refreshStudios(-1, false) } void VirtualStudio::connectionFinished() @@ -1347,196 +1409,217 @@ void VirtualStudio::sendHeartbeat() void VirtualStudio::resetState() { m_webChannelServer->close(); - m_refreshTimer.stop(); m_heartbeatTimer.stop(); m_startTimer.stop(); m_networkOutageTimer.stop(); m_firstRefresh = true; } -void VirtualStudio::getServerList(bool signalRefresh, int index) +void VirtualStudio::refreshStudios(int index, bool signalRefresh) { + // user id is required for retrieval of subscriptions + if (m_userId.isEmpty()) { + std::cerr << "Studio refresh cancelled due to empty user id" << std::endl; + return; + } + // only allow one thread to refresh at a time QMutexLocker refreshLock(&m_refreshMutex); if (m_refreshInProgress) return; - m_refreshInProgress = true; + std::cout << "Refreshing list of studios" << std::endl; + setRefreshInProgress(true); refreshLock.unlock(); + // get subscriptions first; this is required for studio groupings + QNetworkReply* reply = m_api->getSubscriptions(m_userId); + connect(reply, &QNetworkReply::finished, this, [&, reply, signalRefresh, index]() { + if (reply->error() != QNetworkReply::NoError) { + std::cout << "Error: " << reply->errorString().toStdString() << std::endl; + reply->deleteLater(); + return; + } + + QByteArray response = reply->readAll(); + QJsonDocument subscriptionList = QJsonDocument::fromJson(response); + if (!subscriptionList.isArray()) { + std::cout << "Error: Not an array" << std::endl; + reply->deleteLater(); + return; + } + m_subscribedServers.clear(); + QJsonArray subscriptions = subscriptionList.array(); + for (int i = 0; i < subscriptions.count(); i++) { + m_subscribedServers.insert( + subscriptions.at(i)[QStringLiteral("serverId")].toString(), true); + } + reply->deleteLater(); + + // done getting subscriptions; get list of servers next + QNetworkReply* serversReply = m_api->getServers(); + connect(serversReply, &QNetworkReply::finished, this, + [&, serversReply, signalRefresh, index]() { + handleServerUpdate(serversReply, signalRefresh, index); + }); + }); +} + +void VirtualStudio::handleServerUpdate(QNetworkReply* reply, bool signalRefresh, + int index) +{ + if (reply->error() != QNetworkReply::NoError) { + if (signalRefresh) { + emit refreshFinished(index); + } + std::cerr << "Error: " << reply->errorString().toStdString() << std::endl; + reply->deleteLater(); + QMutexLocker refreshLock(&m_refreshMutex); + setRefreshInProgress(false); + return; + } + // Get the serverId of the server at the top of our screen if we know it QString topServerId; if (index >= 0 && index < m_serverModel.count()) { topServerId = m_serverModel.at(index)->id(); } - QNetworkReply* reply = m_api->getServers(); - connect( - reply, &QNetworkReply::finished, this, [&, reply, topServerId, signalRefresh]() { - if (reply->error() != QNetworkReply::NoError) { - if (signalRefresh) { - emit refreshFinished(index); - } - std::cerr << "Error: " << reply->errorString().toStdString() << std::endl; - reply->deleteLater(); - QMutexLocker refreshLock(&m_refreshMutex); - m_refreshInProgress = false; - return; - } - - QByteArray response = reply->readAll(); - QJsonDocument serverList = QJsonDocument::fromJson(response); - reply->deleteLater(); - if (!serverList.isArray()) { - if (signalRefresh) { - emit refreshFinished(index); - } - std::cerr << "Error: Not an array" << std::endl; - QMutexLocker refreshLock(&m_refreshMutex); - m_refreshInProgress = false; - return; - } + QByteArray response = reply->readAll(); + QJsonDocument serverList = QJsonDocument::fromJson(response); + reply->deleteLater(); + if (!serverList.isArray()) { + if (signalRefresh) { + emit refreshFinished(index); + } + std::cerr << "Error: Not an array" << std::endl; + QMutexLocker refreshLock(&m_refreshMutex); + setRefreshInProgress(false); + return; + } - QJsonArray servers = serverList.array(); - // Divide our servers by category initially so that they're easier to sort - QVector yourServers; - QVector subServers; - QVector pubServers; - int skippedStudios = 0; - - QMutexLocker refreshLock(&m_refreshMutex); // protect m_servers - m_servers.clear(); - for (int i = 0; i < servers.count(); i++) { - if (servers.at(i)[QStringLiteral("type")].toString().contains( - QStringLiteral("JackTrip"))) { - QSharedPointer serverInfo(new VsServerInfo(this)); - serverInfo->setIsAdmin( - servers.at(i)[QStringLiteral("admin")].toBool()); - serverInfo->setName(servers.at(i)[QStringLiteral("name")].toString()); - serverInfo->setHost( - servers.at(i)[QStringLiteral("serverHost")].toString()); - serverInfo->setIsManaged( - servers.at(i)[QStringLiteral("managed")].toBool()); - 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->setBannerURL( - servers.at(i)[QStringLiteral("bannerURL")].toString()); - serverInfo->setId(servers.at(i)[QStringLiteral("id")].toString()); - serverInfo->setSessionId( - servers.at(i)[QStringLiteral("sessionId")].toString()); - serverInfo->setStreamId( - servers.at(i)[QStringLiteral("streamId")].toString()); - serverInfo->setInviteKey( - servers.at(i)[QStringLiteral("inviteKey")].toString()); - serverInfo->setCloudId( - servers.at(i)[QStringLiteral("cloudId")].toString()); - serverInfo->setEnabled( - servers.at(i)[QStringLiteral("enabled")].toBool()); - serverInfo->setIsOwner( - servers.at(i)[QStringLiteral("owner")].toBool()); - - // Always add servers to m_servers - m_servers.append(serverInfo); - - // Only add servers to the model that we want to show - if (serverInfo->isAdmin() || serverInfo->isOwner()) { - if (filterStudio(*serverInfo)) { - ++skippedStudios; - } else { - yourServers.append(serverInfo); - serverInfo->setSection(VsServerInfo::YOUR_STUDIOS); - } - } else if (m_subscribedServers.contains(serverInfo->id())) { - if (filterStudio(*serverInfo)) { - ++skippedStudios; - } else { - subServers.append(serverInfo); - serverInfo->setSection(VsServerInfo::SUBSCRIBED_STUDIOS); - } - } else { - if (!filterStudio(*serverInfo)) { - pubServers.append(serverInfo); - serverInfo->setSection(VsServerInfo::PUBLIC_STUDIOS); - } - // don't count public studios in skipped count - } + QJsonArray servers = serverList.array(); + // Divide our servers by category initially so that they're easier to sort + QVector yourServers; + QVector subServers; + QVector pubServers; + int skippedStudios = 0; + + QMutexLocker refreshLock(&m_refreshMutex); // protect m_servers + m_servers.clear(); + for (int i = 0; i < servers.count(); i++) { + if (servers.at(i)[QStringLiteral("type")].toString().contains( + QStringLiteral("JackTrip"))) { + QSharedPointer serverInfo(new VsServerInfo(this)); + serverInfo->setIsAdmin(servers.at(i)[QStringLiteral("admin")].toBool()); + serverInfo->setName(servers.at(i)[QStringLiteral("name")].toString()); + serverInfo->setHost(servers.at(i)[QStringLiteral("serverHost")].toString()); + serverInfo->setIsManaged(servers.at(i)[QStringLiteral("managed")].toBool()); + 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->setBannerURL( + servers.at(i)[QStringLiteral("bannerURL")].toString()); + serverInfo->setId(servers.at(i)[QStringLiteral("id")].toString()); + serverInfo->setSessionId( + servers.at(i)[QStringLiteral("sessionId")].toString()); + serverInfo->setStreamId(servers.at(i)[QStringLiteral("streamId")].toString()); + serverInfo->setInviteKey( + servers.at(i)[QStringLiteral("inviteKey")].toString()); + serverInfo->setCloudId(servers.at(i)[QStringLiteral("cloudId")].toString()); + serverInfo->setEnabled(servers.at(i)[QStringLiteral("enabled")].toBool()); + serverInfo->setIsOwner(servers.at(i)[QStringLiteral("owner")].toBool()); + + // Always add servers to m_servers + m_servers.append(serverInfo); + + // Only add servers to the model that we want to show + if (serverInfo->isAdmin() || serverInfo->isOwner()) { + if (filterStudio(*serverInfo)) { + ++skippedStudios; + } else { + yourServers.append(serverInfo); + serverInfo->setSection(VsServerInfo::YOUR_STUDIOS); } - } - refreshLock.unlock(); - - // sort studios in each section by name - auto serverSorter = [](VsServerInfoPointer first, - VsServerInfoPointer second) { - return *first < *second; - }; - std::sort(yourServers.begin(), yourServers.end(), serverSorter); - std::sort(subServers.begin(), subServers.end(), serverSorter); - std::sort(pubServers.begin(), pubServers.end(), serverSorter); - - // 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"); - - if (skippedStudios == 0) { - // This is a new user - setShowCreateStudio(true); - } else { - // This is not a new user. One or more studios were filtered. - setShowCreateStudio(false); - } + } else if (m_subscribedServers.contains(serverInfo->id())) { + if (filterStudio(*serverInfo)) { + ++skippedStudios; } else { - m_logoSection = QStringLiteral("Subscribed Studios"); + subServers.append(serverInfo); + serverInfo->setSection(VsServerInfo::SUBSCRIBED_STUDIOS); } } else { - m_logoSection = QStringLiteral("Your Studios"); - } - emit logoSectionChanged(); - - m_serverModel.clear(); - for (const VsServerInfoPointer& s : yourServers) { - m_serverModel.append(s.get()); - } - for (const VsServerInfoPointer& s : subServers) { - m_serverModel.append(s.get()); - } - for (const VsServerInfoPointer& s : pubServers) { - m_serverModel.append(s.get()); - } - emit serverModelChanged(); - int index = -1; - if (!topServerId.isEmpty()) { - for (int i = 0; i < m_serverModel.count(); i++) { - if (m_serverModel.at(i)->id() == topServerId) { - index = i; - break; - } + if (!filterStudio(*serverInfo) && serverInfo->enabled()) { + pubServers.append(serverInfo); + serverInfo->setSection(VsServerInfo::PUBLIC_STUDIOS); } + // don't count public studios in skipped count } - if (m_firstRefresh) { - m_refreshTimer.setInterval(5000); - m_refreshTimer.start(); - m_heartbeatTimer.setInterval(5000); - m_heartbeatTimer.start(); - m_firstRefresh = false; + } + } + refreshLock.unlock(); + + // sort studios in each section by name + auto serverSorter = [](VsServerInfoPointer first, VsServerInfoPointer second) { + return *first < *second; + }; + std::sort(yourServers.begin(), yourServers.end(), serverSorter); + std::sort(subServers.begin(), subServers.end(), serverSorter); + std::sort(pubServers.begin(), pubServers.end(), serverSorter); + + // 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"); + if (skippedStudios == 0 && m_windowState == "browse") { + // Transition to create studio page + setWindowState("create_studio"); } - m_refreshInProgress = false; - if (signalRefresh) { - emit refreshFinished(index); + } else { + m_logoSection = QStringLiteral("Subscribed Studios"); + } + } else { + m_logoSection = QStringLiteral("Your Studios"); + } + emit logoSectionChanged(); + + m_serverModel.clear(); + for (const VsServerInfoPointer& s : yourServers) { + m_serverModel.append(s.get()); + } + for (const VsServerInfoPointer& s : subServers) { + m_serverModel.append(s.get()); + } + for (const VsServerInfoPointer& s : pubServers) { + m_serverModel.append(s.get()); + } + emit serverModelChanged(); + + index = -1; + if (!topServerId.isEmpty()) { + for (int i = 0; i < m_serverModel.count(); i++) { + if (m_serverModel.at(i)->id() == topServerId) { + index = i; + break; } - }); + } + } + + if (m_firstRefresh) { + m_heartbeatTimer.setInterval(5000); + m_heartbeatTimer.start(); + m_firstRefresh = false; + } + setRefreshInProgress(false); + if (signalRefresh) { + emit refreshFinished(index); + } } bool VirtualStudio::filterStudio(const VsServerInfo& serverInfo) const @@ -1553,37 +1636,6 @@ bool VirtualStudio::filterStudio(const VsServerInfo& serverInfo) const return false; } -void VirtualStudio::getSubscriptions() -{ - if (m_userId.isEmpty()) { - qDebug() << "Invalid user ID"; - return; - } - QNetworkReply* reply = m_api->getSubscriptions(m_userId); - connect(reply, &QNetworkReply::finished, this, [&, reply]() { - if (reply->error() != QNetworkReply::NoError) { - std::cout << "Error: " << reply->errorString().toStdString() << std::endl; - reply->deleteLater(); - return; - } - - QByteArray response = reply->readAll(); - QJsonDocument subscriptionList = QJsonDocument::fromJson(response); - if (!subscriptionList.isArray()) { - std::cout << "Error: Not an array" << std::endl; - reply->deleteLater(); - return; - } - m_subscribedServers.clear(); - QJsonArray subscriptions = subscriptionList.array(); - for (int i = 0; i < subscriptions.count(); i++) { - m_subscribedServers.insert( - subscriptions.at(i)[QStringLiteral("serverId")].toString(), true); - } - reply->deleteLater(); - }); -} - void VirtualStudio::getRegions() { QNetworkReply* reply = m_api->getRegions(m_userId); @@ -1613,6 +1665,7 @@ void VirtualStudio::getUserMetadata() m_userMetadata = QJsonDocument::fromJson(reply->readAll()).object(); emit userMetadataChanged(); reply->deleteLater(); + refreshStudios(-1, false); }); } @@ -1633,6 +1686,41 @@ void VirtualStudio::detectedFeedbackLoop() VirtualStudio::~VirtualStudio() { QDesktopServices::unsetUrlHandler("jacktrip"); + // close the window + m_view.reset(); // stop the audio worker thread before destructing other things m_audioConfigPtr.reset(); + // stop device and corresponding threads + m_devicePtr.reset(); +} + +QApplication* VirtualStudio::createApplication(int& argc, char* argv[]) +{ +#if defined(Q_OS_WIN) + // Fix for display scaling like 125% or 150% on Windows + QGuiApplication::setHighDpiScaleFactorRoundingPolicy( + Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); + + // Enables resource sharing between the OpenGL contexts + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + // QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); + // QCoreApplication::setAttribute(Qt::AA_UseOpenGLES); + + // QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D11); + QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); +#endif + + QQuickStyle::setStyle("Basic"); + + // Initialize webengine + QtWebEngineQuick::initialize(); + // TODO: Add support for QtWebView + // qputenv("QT_WEBVIEW_PLUGIN", "native"); + // QtWebView::initialize(); + +#if defined(Q_OS_MACOS) + return new JTApplication(argc, argv); +#else + return new QApplication(argc, argv); +#endif } diff --git a/src/gui/virtualstudio.h b/src/vs/virtualstudio.h similarity index 88% rename from src/gui/virtualstudio.h rename to src/vs/virtualstudio.h index fbd02c6..8861af5 100644 --- a/src/gui/virtualstudio.h +++ b/src/vs/virtualstudio.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -42,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -53,20 +53,17 @@ #include #include "../Settings.h" -#include "qjacktrip.h" +#include "../UserInterface.h" #include "vsConstants.h" #include "vsQuickView.h" #include "vsServerInfo.h" -#ifdef __APPLE__ -#include "NoNap.h" -#endif - class JackTrip; class VsAudio; class VsApi; class VsAuth; class VsDevice; +class VsDeeplink; class VsWebSocket; typedef QSharedPointer VsServerInfoPointer; @@ -75,10 +72,10 @@ class VirtualStudio : public QObject { Q_OBJECT Q_PROPERTY(int webChannelPort READ webChannelPort NOTIFY webChannelPortChanged) - Q_PROPERTY(bool showFirstRun READ showFirstRun WRITE setShowFirstRun NOTIFY - showFirstRunChanged) Q_PROPERTY(bool hasRefreshToken READ hasRefreshToken NOTIFY hasRefreshTokenChanged) Q_PROPERTY(QString versionString READ versionString CONSTANT) + Q_PROPERTY(QString buildString READ buildString CONSTANT) + Q_PROPERTY(QString copyrightString READ copyrightString CONSTANT) Q_PROPERTY(QString logoSection READ logoSection NOTIFY logoSectionChanged) Q_PROPERTY( QString connectedErrorMsg READ connectedErrorMsg NOTIFY connectedErrorMsgChanged) @@ -94,8 +91,6 @@ class VirtualStudio : public QObject showInactiveChanged) Q_PROPERTY(bool showSelfHosted READ showSelfHosted WRITE setShowSelfHosted NOTIFY showSelfHostedChanged) - Q_PROPERTY(bool showCreateStudio READ showCreateStudio WRITE setShowCreateStudio - NOTIFY showCreateStudioChanged) Q_PROPERTY(QString connectionState READ connectionState NOTIFY connectionStateChanged) Q_PROPERTY(QJsonObject networkStats READ networkStats NOTIFY networkStatsChanged) Q_PROPERTY(bool networkOutage READ networkOutage NOTIFY updatedNetworkOutage) @@ -113,6 +108,8 @@ class VirtualStudio : public QObject Q_PROPERTY(bool showWarnings READ showWarnings WRITE setShowWarnings NOTIFY showWarningsChanged) Q_PROPERTY(bool isExiting READ isExiting NOTIFY isExitingChanged) + Q_PROPERTY( + bool refreshInProgress READ refreshInProgress NOTIFY refreshInProgressChanged) Q_PROPERTY(bool noUpdater READ noUpdater CONSTANT) Q_PROPERTY(bool psiBuild READ psiBuild CONSTANT) Q_PROPERTY(QString failedMessage READ failedMessage NOTIFY failedMessageChanged) @@ -120,23 +117,23 @@ class VirtualStudio : public QObject windowStateUpdated) Q_PROPERTY(QString apiHost READ apiHost WRITE setApiHost NOTIFY apiHostChanged) Q_PROPERTY(bool vsFtux READ vsFtux CONSTANT) + Q_PROPERTY(bool hasClassicMode READ hasClassicMode CONSTANT) Q_PROPERTY( QStringList updateChannelComboModel READ getUpdateChannelComboModel CONSTANT) public: - explicit VirtualStudio(bool firstRun = false, QObject* parent = nullptr); + explicit VirtualStudio(UserInterface& parent); ~VirtualStudio() override; - void setStandardWindow(QSharedPointer window); - void setCLISettings(QSharedPointer settings); void show(); + void hide(); void raiseToTop(); int webChannelPort(); - bool showFirstRun(); - void setShowFirstRun(bool show); bool hasRefreshToken(); QString versionString(); + QString buildString(); + QString copyrightString(); QString logoSection(); QString connectedErrorMsg(); void setConnectedErrorMsg(const QString& msg); @@ -152,8 +149,6 @@ class VirtualStudio : public QObject void setShowInactive(bool inactive); bool showSelfHosted(); void setShowSelfHosted(bool selfHosted); - bool showCreateStudio(); - void setShowCreateStudio(bool createStudio); float fontScale(); float uiScale(); void setUiScale(float scale); @@ -178,22 +173,27 @@ class VirtualStudio : public QObject QString apiHost(); void setApiHost(QString host); bool vsFtux(); + bool hasClassicMode(); bool isExiting(); + bool refreshInProgress(); + void setRefreshInProgress(bool b); + + static QApplication* createApplication(int& argc, char* argv[]); + const QStringList& getUpdateChannelComboModel() const { return m_updateChannelOptions; } public slots: - void toStandard(); - void toVirtualStudio(); + void toVirtualStudioMode(); + void toClassicMode(); void login(); void logout(); void refreshStudios(int index, bool signalRefresh = false); void loadSettings(); void saveSettings(); void triggerReconnect(bool refresh); - void createStudio(); void editProfile(); void showAbout(); void openLink(const QString& url); @@ -210,7 +210,6 @@ class VirtualStudio : public QObject void disconnected(); void refreshFinished(int index); void webChannelPortChanged(int webChannelPort); - void showFirstRunChanged(); void hasRefreshTokenChanged(); void logoSectionChanged(); void connectedErrorMsgChanged(); @@ -220,7 +219,6 @@ class VirtualStudio : public QObject void userMetadataChanged(); void showInactiveChanged(); void showSelfHostedChanged(); - void showCreateStudioChanged(); void connectionStateChanged(); void networkStatsChanged(); void updateChannelChanged(); @@ -232,15 +230,17 @@ class VirtualStudio : public QObject void darkModeChanged(); void testModeChanged(); void signalExit(); - void periodicRefresh(); void failedMessageChanged(); void studioToJoinChanged(); void updatedNetworkOutage(bool outage); void windowStateUpdated(); void isExitingChanged(); + void scheduleStudioRefresh(int index, bool signalRefresh); + void refreshInProgressChanged(); void apiHostChanged(); void feedbackDetected(); void openFeedbackSurveyModal(QString serverId); + void openAboutWindow(); private slots: void slotAuthSucceeded(); @@ -256,9 +256,8 @@ class VirtualStudio : public QObject private: void resetState(); - void getServerList(bool signalRefresh = false, int index = -1); + void handleServerUpdate(QNetworkReply* reply, bool signalRefresh, int index); bool filterStudio(const VsServerInfo& serverInfo) const; - void getSubscriptions(); void getRegions(); void getUserMetadata(); bool readyToJoin(); @@ -272,11 +271,11 @@ class VirtualStudio : public QObject RECONNECTING_REFRESH }; - VsQuickView m_view; + UserInterface& m_interface; VsServerInfo m_currentStudio; QNetworkAccessManager* m_networkAccessManagerPtr; - QSharedPointer m_standardWindow; - QSharedPointer m_cliSettings; + QScopedPointer m_view; + QSharedPointer m_deepLinkPtr; QSharedPointer m_auth; QSharedPointer m_api; QScopedPointer m_devicePtr; @@ -291,7 +290,6 @@ class VirtualStudio : public QObject QJsonObject m_userMetadata; QJsonObject m_networkStats; QTimer m_startTimer; - QTimer m_refreshTimer; QTimer m_heartbeatTimer; QTimer m_networkOutageTimer; QMutex m_refreshMutex; @@ -301,18 +299,15 @@ class VirtualStudio : public QObject QString m_userId; QString m_apiHost = PROD_API_HOST; ReconnectState m_reconnectState = ReconnectState::NOT_RECONNECTING; - QJackTrip::uiModeT m_uiMode = QJackTrip::UNSET; bool m_firstRefresh = true; bool m_jackTripRunning = false; - bool m_showFirstRun = false; bool m_checkSsl = true; bool m_refreshInProgress = false; bool m_onConnectedScreen = false; bool m_isExiting = false; bool m_showInactive = true; - bool m_showSelfHosted = false; - bool m_showCreateStudio = false; + bool m_showSelfHosted = true; bool m_showDeviceSetup = true; bool m_showWarnings = true; bool m_darkMode = false; @@ -324,17 +319,13 @@ class VirtualStudio : public QObject uint32_t m_webChannelPort = 1; QString m_failedMessage = QStringLiteral(""); - QString m_windowState = QStringLiteral("start"); + QString m_windowState = QStringLiteral(""); QString m_connectedErrorMsg = QStringLiteral(""); QString m_logoSection = QStringLiteral("Your Studios"); QString m_connectionState = QStringLiteral("Waiting..."); QStringList m_updateChannelOptions = {"Stable", "Edge"}; -#ifdef __APPLE__ - NoNap m_noNap; -#endif - -#ifdef VS_FTUX +#if defined(VS_FTUX) || defined(NO_CLASSIC) bool m_vsFtux = true; #else bool m_vsFtux = false; diff --git a/src/gui/vs.qml b/src/vs/vs.qml similarity index 98% rename from src/gui/vs.qml rename to src/vs/vs.qml index 9eee794..360a12a 100644 --- a/src/gui/vs.qml +++ b/src/vs/vs.qml @@ -183,46 +183,57 @@ Rectangle { FirstLaunch { id: startScreen + x: -startScreen.width } Login { id: loginScreen + x: window.width } Recommendations { id: recommendationsScreen + x: window.width } Permissions { id: permissionsScreen + x: window.width } Browse { id: browseScreen + x: window.width } Setup { id: setupScreen + x: window.width } Settings { id: settingsScreen + x: window.width } Connected { id: connectedScreen + x: window.width } ChangeDevices { id: changeDevicesScreen + x: window.width } CreateStudio { id: createStudioScreen + x: window.width } Failed { id: failedScreen + x: window.width } onWidthChanged: { diff --git a/src/gui/qjacktrip.qrc b/src/vs/vs.qrc similarity index 95% rename from src/gui/qjacktrip.qrc rename to src/vs/vs.qrc index 7f5e0dc..0b847fe 100644 --- a/src/gui/qjacktrip.qrc +++ b/src/vs/vs.qrc @@ -1,11 +1,7 @@ - - about@2x.png - about.png - icon.png - vs.qml + AboutWindow.qml FirstLaunch.qml Login.qml LearnMoreButton.qml @@ -29,6 +25,7 @@ DeviceControlsGroup.qml DeviceRefreshButton.qml DeviceWarning.qml + DeviceWarningModal.qml InfoTooltip.qml Web.qml WebView.qml diff --git a/src/gui/vsApi.cpp b/src/vs/vsApi.cpp similarity index 98% rename from src/gui/vsApi.cpp rename to src/vs/vsApi.cpp index 2a50de2..513390b 100644 --- a/src/gui/vsApi.cpp +++ b/src/vs/vsApi.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsApi.h b/src/vs/vsApi.h similarity index 95% rename from src/gui/vsApi.h rename to src/vs/vsApi.h index 2d64f0d..9b1d713 100644 --- a/src/gui/vsApi.h +++ b/src/vs/vsApi.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsAudio.cpp b/src/vs/vsAudio.cpp similarity index 98% rename from src/gui/vsAudio.cpp rename to src/vs/vsAudio.cpp index a9fda8f..6964c77 100644 --- a/src/gui/vsAudio.cpp +++ b/src/vs/vsAudio.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -212,6 +211,15 @@ bool VsAudio::jackIsAvailable() const } } +bool VsAudio::asioIsAvailable() const +{ +#if defined(RT_AUDIO) && defined(_WIN32) + return RtAudio::getCompiledApiByName("asio") != RtAudio::UNSPECIFIED; +#else + return false; +#endif +} + void VsAudio::setAudioReady(bool ready) { if (ready == m_audioReady) @@ -271,12 +279,14 @@ void VsAudio::setBufferSize(int bufSize) emit bufferSizeChanged(); } -void VsAudio::setBufferStrategy(int bufStrategy) +void VsAudio::setQueueBuffer(int queueBuffer) { - if (m_bufferStrategy == bufStrategy) + if (m_queueBuffer == queueBuffer) return; - m_bufferStrategy = bufStrategy; - emit bufferStrategyChanged(); + if (queueBuffer < 0) + queueBuffer = 0; + m_queueBuffer = queueBuffer; + emit queueBufferChanged(); } void VsAudio::setNumInputChannels(int numChannels) @@ -532,10 +542,7 @@ void VsAudio::loadSettings() } setBufferSize(settings.value(QStringLiteral("BufferSize"), 128).toInt()); - int buffer_strategy = settings.value(QStringLiteral("BufferStrategy"), 2).toInt(); - if (buffer_strategy == 3 || buffer_strategy == 4) - buffer_strategy = 2; - setBufferStrategy(buffer_strategy); + setQueueBuffer(settings.value(QStringLiteral("QueueBuffer"), 0).toInt()); setFeedbackDetectionEnabled( settings.value(QStringLiteral("FeedbackDetectionEnabled"), true).toBool()); settings.endGroup(); @@ -558,7 +565,7 @@ void VsAudio::saveSettings() settings.setValue(QStringLiteral("BaseOutputChannel"), m_baseOutputChannel); settings.setValue(QStringLiteral("NumOutputChannels"), m_numOutputChannels); settings.setValue(QStringLiteral("BufferSize"), m_audioBufferSize); - settings.setValue(QStringLiteral("BufferStrategy"), m_bufferStrategy); + settings.setValue(QStringLiteral("QueueBuffer"), m_queueBuffer); settings.setValue(QStringLiteral("FeedbackDetectionEnabled"), m_feedbackDetectionEnabled); settings.endGroup(); @@ -1114,7 +1121,9 @@ void VsAudioWorker::getDeviceList(const QVector& devices, } // Skip blacklisted devices - if (blacklisted_devices.contains(deviceName)) { + const bool iPhoneMic = deviceName.startsWith("Apple Inc.:") + && deviceName.endsWith("Phone Microphone"); + if (blacklisted_devices.contains(deviceName) || iPhoneMic) { std::cout << "RTAudio: blacklisted " << (isInput ? "input" : "output") << " device: " << devices[n].name << std::endl; continue; diff --git a/src/gui/vsAudio.h b/src/vs/vsAudio.h similarity index 94% rename from src/gui/vsAudio.h rename to src/vs/vsAudio.h index ab53fb6..c75b9fc 100644 --- a/src/gui/vsAudio.h +++ b/src/vs/vsAudio.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -48,6 +47,7 @@ #include "../AudioInterface.h" #include "../jacktrip_globals.h" +#include "vsPermissions.h" #ifdef RT_AUDIO #include "../RtAudioInterface.h" @@ -60,7 +60,6 @@ class Monitor; class QThread; class Tone; class Volume; -class VsPermissions; class VsAudioWorker; class VsAudio : public QObject @@ -82,8 +81,8 @@ class VsAudio : public QObject int sampleRate READ getSampleRate WRITE setSampleRate NOTIFY sampleRateChanged) Q_PROPERTY( int bufferSize READ getBufferSize WRITE setBufferSize NOTIFY bufferSizeChanged) - Q_PROPERTY(int bufferStrategy READ getBufferStrategy WRITE setBufferStrategy NOTIFY - bufferStrategyChanged) + Q_PROPERTY(int queueBuffer READ getQueueBuffer WRITE setQueueBuffer NOTIFY + queueBufferChanged) Q_PROPERTY(int numInputChannels READ getNumInputChannels WRITE setNumInputChannels NOTIFY numInputChannelsChanged) Q_PROPERTY(int numOutputChannels READ getNumOutputChannels WRITE setNumOutputChannels @@ -122,11 +121,7 @@ class VsAudio : public QObject outputChannelsComboModelChanged) Q_PROPERTY(QJsonArray inputMixModeComboModel READ getInputMixModeComboModel NOTIFY inputMixModeComboModelChanged) - Q_PROPERTY(QStringList feedbackDetectionComboModel READ getFeedbackDetectionComboModel - CONSTANT) Q_PROPERTY(QStringList bufferSizeComboModel READ getBufferSizeComboModel CONSTANT) - Q_PROPERTY( - QStringList bufferStrategyComboModel READ getBufferStrategyComboModel CONSTANT) Q_PROPERTY(QStringList audioBackendComboModel READ getAudioBackendComboModel CONSTANT) Q_PROPERTY( QString devicesWarning READ getDevicesWarningMsg NOTIFY devicesWarningChanged) @@ -161,6 +156,7 @@ class VsAudio : public QObject // getters for state shared with QML bool backendAvailable() const; bool jackIsAvailable() const; + bool asioIsAvailable() const; bool getAudioReady() const { return m_audioReady; } bool getScanningDevices() const { return m_scanningDevices; } bool getFeedbackDetectionEnabled() const { return m_feedbackDetectionEnabled; } @@ -172,7 +168,7 @@ class VsAudio : public QObject } int getSampleRate() const { return m_audioSampleRate; } int getBufferSize() const { return m_audioBufferSize; } - int getBufferStrategy() const { return m_bufferStrategy; } + int getQueueBuffer() const { return m_queueBuffer; } int getNumInputChannels() const { return getUseRtAudio() ? m_numInputChannels : 2; } int getNumOutputChannels() const { return getUseRtAudio() ? m_numOutputChannels : 2; } int getBaseInputChannel() const { return getUseRtAudio() ? m_baseInputChannel : 0; } @@ -202,15 +198,7 @@ class VsAudio : public QObject { return m_inputMixModeComboModel; } - const QStringList& getFeedbackDetectionComboModel() const - { - return m_feedbackDetectionComboModel; - } const QStringList& getBufferSizeComboModel() const { return m_bufferSizeComboModel; } - const QStringList& getBufferStrategyComboModel() const - { - return m_bufferStrategyComboModel; - } const QStringList& getAudioBackendComboModel() const { return m_audioBackendComboModel; @@ -227,7 +215,7 @@ class VsAudio : public QObject void setAudioBackend(const QString& backend); void setSampleRate(int sampleRate); void setBufferSize(int bufSize); - void setBufferStrategy(int bufStrategy); + void setQueueBuffer(int queueBuffer); void setNumInputChannels(int numChannels); void setNumOutputChannels(int numChannels); void setBaseInputChannel(int baseChannel); @@ -264,7 +252,7 @@ class VsAudio : public QObject void audioBackendChanged(bool useRtAudio); void sampleRateChanged(); void bufferSizeChanged(); - void bufferStrategyChanged(); + void queueBufferChanged(); void numInputChannelsChanged(int numChannels); void numOutputChannelsChanged(int numChannels); void baseInputChannelChanged(int baseChannel); @@ -338,7 +326,7 @@ class VsAudio : public QObject int m_audioSampleRate = gDefaultSampleRate; int m_audioBufferSize = gDefaultBufferSizeInSamples; ///< Audio buffer size to process on each callback - int m_bufferStrategy = 0; + int m_queueBuffer = 0; int m_numInputChannels = gDefaultNumInChannels; int m_numOutputChannels = gDefaultNumOutChannels; int m_baseInputChannel = 0; @@ -383,11 +371,8 @@ class VsAudio : public QObject Analyzer* m_outputAnalyzerPluginPtr; #endif - QStringList m_audioBackendComboModel = {"JACK", "RtAudio"}; - QStringList m_feedbackDetectionComboModel = {"Enabled", "Disabled"}; + QStringList m_audioBackendComboModel = {"JACK", "RtAudio"}; QStringList m_bufferSizeComboModel = {"16", "32", "64", "128", "256", "512", "1024"}; - QStringList m_bufferStrategyComboModel = { - "Adaptable Latency (Old)", "Stable Latency (Old)", "Loss Concealment (Default)"}; friend class VsAudioWorker; }; diff --git a/src/gui/vsAuth.cpp b/src/vs/vsAuth.cpp similarity index 90% rename from src/gui/vsAuth.cpp rename to src/vs/vsAuth.cpp index 970855c..25d8427 100644 --- a/src/gui/vsAuth.cpp +++ b/src/vs/vsAuth.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -91,7 +90,7 @@ void VsAuth::fetchUserInfo(QString accessToken) if (reply->error() != QNetworkReply::NoError) { std::cout << "VsAuth::fetchUserInfo Error: " << reply->errorString().toStdString() << std::endl; - handleAuthFailed(); // handle failure + handleAuthFailed(reply->errorString()); // handle failure emit fetchUserInfoFailed(); reply->deleteLater(); return; @@ -105,7 +104,7 @@ void VsAuth::fetchUserInfo(QString accessToken) if (userId.isEmpty()) { std::cout << "VsAuth::fetchUserInfo Error: empty userId" << std::endl; - handleAuthFailed(); // handle failure + handleAuthFailed("empty userId"); // handle failure emit fetchUserInfoFailed(); return; } @@ -136,10 +135,11 @@ void VsAuth::refreshAccessToken(QString refreshToken) QByteArray buffer = reply->readAll(); // Error: failed to get device code - if (reply->error()) { - std::cout << "Failed to get new access token: " << buffer.toStdString() - << std::endl; - handleAuthFailed(); // handle failure + QNetworkReply::NetworkError err = reply->error(); + if (err != QNetworkReply::NoError) { + std::cout << "Failed to get new access token: " + << reply->errorString().toStdString() << std::endl; + handleAuthFailed(reply->errorString()); // handle failure emit refreshTokenFailed(); reply->deleteLater(); return; @@ -151,7 +151,7 @@ void VsAuth::refreshAccessToken(QString refreshToken) if (parseError.error) { std::cout << "Error parsing JSON for Access Token: " << parseError.errorString().toStdString() << std::endl; - handleAuthFailed(); // handle failure + handleAuthFailed("error parsing access token"); // handle failure emit refreshTokenFailed(); reply->deleteLater(); return; @@ -196,9 +196,11 @@ void VsAuth::handleRefreshSucceeded(QString accessToken) m_accessToken = accessToken; m_authenticationStage = QStringLiteral("success"); + m_errorMessage = QStringLiteral(""); m_attemptingRefreshToken = false; emit updatedAuthenticationStage(m_authenticationStage); + emit updatedErrorMessage(m_errorMessage); emit updatedVerificationCode(m_verificationCode); emit updatedAttemptingRefreshToken(m_attemptingRefreshToken); } @@ -220,11 +222,13 @@ void VsAuth::handleAuthSucceeded(QString userId, QString accessToken) m_verificationCode = QStringLiteral(""); m_accessToken = accessToken; m_authenticationStage = QStringLiteral("success"); + m_errorMessage = QStringLiteral(""); m_attemptingRefreshToken = false; m_isAuthenticated = true; emit updatedUserId(m_userId); emit updatedAuthenticationStage(m_authenticationStage); + emit updatedErrorMessage(m_errorMessage); emit updatedVerificationCode(m_verificationCode); emit updatedIsAuthenticated(m_isAuthenticated); emit updatedAttemptingRefreshToken(m_attemptingRefreshToken); @@ -234,7 +238,7 @@ void VsAuth::handleAuthSucceeded(QString userId, QString accessToken) emit authSucceeded(); } -void VsAuth::handleAuthFailed() +void VsAuth::handleAuthFailed(QString errorMessage) { // this might get called because there was an error getting the access token, // or there was an issue fetching the user ID. We need both to say @@ -245,12 +249,14 @@ void VsAuth::handleAuthFailed() m_verificationCode = QStringLiteral(""); m_accessToken = QStringLiteral(""); m_authenticationStage = QStringLiteral("failed"); + m_errorMessage = errorMessage; m_authenticationMethod = QStringLiteral(""); m_attemptingRefreshToken = false; m_isAuthenticated = false; emit updatedUserId(m_userId); emit updatedAuthenticationStage(m_authenticationStage); + emit updatedErrorMessage(m_errorMessage); emit updatedVerificationCode(m_verificationCode); emit updatedIsAuthenticated(m_isAuthenticated); emit updatedAttemptingRefreshToken(m_attemptingRefreshToken); @@ -269,10 +275,12 @@ void VsAuth::cancelAuthenticationFlow() m_verificationCode = QStringLiteral(""); m_accessToken = QStringLiteral(""); m_authenticationStage = QStringLiteral("unauthenticated"); + m_errorMessage = QStringLiteral("cancelled"); m_isAuthenticated = false; emit updatedUserId(m_userId); emit updatedAuthenticationStage(m_authenticationStage); + emit updatedErrorMessage(m_errorMessage); emit updatedVerificationCode(m_verificationCode); emit updatedIsAuthenticated(m_isAuthenticated); } @@ -289,10 +297,12 @@ void VsAuth::logout() m_verificationCode = QStringLiteral(""); m_accessToken = QStringLiteral(""); m_authenticationStage = QStringLiteral("unauthenticated"); + m_errorMessage = QStringLiteral(""); m_isAuthenticated = false; emit updatedUserId(m_userId); emit updatedAuthenticationStage(m_authenticationStage); + emit updatedErrorMessage(m_errorMessage); emit updatedVerificationCode(m_verificationCode); emit updatedIsAuthenticated(m_isAuthenticated); } \ No newline at end of file diff --git a/src/gui/vsAuth.h b/src/vs/vsAuth.h similarity index 93% rename from src/gui/vsAuth.h rename to src/vs/vsAuth.h index 61ccb85..e79116a 100644 --- a/src/gui/vsAuth.h +++ b/src/vs/vsAuth.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -53,6 +52,7 @@ class VsAuth : public QObject Q_PROPERTY(QString authenticationStage READ authenticationStage NOTIFY updatedAuthenticationStage); + Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY updatedErrorMessage); Q_PROPERTY(QString verificationCode READ deviceCode NOTIFY updatedVerificationCode); Q_PROPERTY( QString verificationUrl READ deviceVerificationUrl NOTIFY updatedVerificationUrl); @@ -77,6 +77,7 @@ class VsAuth : public QObject // getter methods QString authenticationStage() { return m_authenticationStage; }; + QString errorMessage() { return m_errorMessage; }; QString deviceCode() { return m_verificationCode; }; QString deviceVerificationUrl() { return m_verificationUrl; }; bool isAuthenticated() { return m_isAuthenticated; }; @@ -88,6 +89,7 @@ class VsAuth : public QObject signals: void updatedAuthenticationStage(QString authenticationStage); + void updatedErrorMessage(QString errorMessage); void updatedVerificationCode(QString deviceCode); void updatedVerificationUrl(QUrl verificationUrl); void updatedIsAuthenticated(bool isAuthenticated); @@ -103,7 +105,7 @@ class VsAuth : public QObject private slots: void handleRefreshSucceeded(QString accessToken); void handleAuthSucceeded(QString userId, QString accessToken); - void handleAuthFailed(); + void handleAuthFailed(QString errorMessage); void initializedCodeFlow(QString code, QString verificationUrl); void codeFlowCompleted(QString accessToken, QString refreshToken); void codeExpired(); @@ -115,6 +117,7 @@ class VsAuth : public QObject QString m_authorizationServerHost; QString m_authenticationStage = QStringLiteral("unauthenticated"); + QString m_errorMessage = QStringLiteral(""); QString m_verificationCode = QStringLiteral(""); QString m_verificationUrl; QString m_authenticationMethod = QStringLiteral(""); diff --git a/src/gui/vsConstants.h b/src/vs/vsConstants.h similarity index 94% rename from src/gui/vsConstants.h rename to src/vs/vsConstants.h index 63f3f39..83bb423 100644 --- a/src/gui/vsConstants.h +++ b/src/vs/vsConstants.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsDeeplink.cpp b/src/vs/vsDeeplink.cpp similarity index 98% rename from src/gui/vsDeeplink.cpp rename to src/vs/vsDeeplink.cpp index d3cafd1..a602aba 100644 --- a/src/gui/vsDeeplink.cpp +++ b/src/vs/vsDeeplink.cpp @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2023 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsDeeplink.h b/src/vs/vsDeeplink.h similarity index 97% rename from src/gui/vsDeeplink.h rename to src/vs/vsDeeplink.h index 8eb932e..464073b 100644 --- a/src/gui/vsDeeplink.h +++ b/src/vs/vsDeeplink.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2023 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsDevice.cpp b/src/vs/vsDevice.cpp similarity index 98% rename from src/gui/vsDevice.cpp rename to src/vs/vsDevice.cpp index 79b99b9..4f036df 100644 --- a/src/gui/vsDevice.cpp +++ b/src/vs/vsDevice.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -278,7 +277,7 @@ JackTrip* VsDevice::initJackTrip( [[maybe_unused]] std::string output, [[maybe_unused]] int baseInputChannel, [[maybe_unused]] int numChannelsIn, [[maybe_unused]] int baseOutputChannel, [[maybe_unused]] int numChannelsOut, [[maybe_unused]] int inputMixMode, - [[maybe_unused]] int bufferSize, [[maybe_unused]] int bufferStrategy, + [[maybe_unused]] int bufferSize, [[maybe_unused]] int queueBuffer, VsServerInfo* studioInfo) { m_jackTrip.reset( @@ -305,8 +304,10 @@ JackTrip* VsDevice::initJackTrip( } m_jackTrip->setBindPorts(bindPort); m_jackTrip->setRemoteClientName(m_appID); - m_jackTrip->setBufferStrategy(bufferStrategy); - m_jackTrip->setBufferQueueLength(-500); // use -q auto + m_jackTrip->setBufferStrategy(3); // PLC + m_jackTrip->setBufferQueueLength(queueBuffer); + m_jackTrip->setUseRtUdpPriority( + true); // rt udp priority reduces glitches on desktops m_jackTrip->setPeerAddress(studioInfo->host()); m_jackTrip->setPeerPorts(studioInfo->port()); m_jackTrip->setPeerHandshakePort(studioInfo->port()); diff --git a/src/gui/vsDevice.h b/src/vs/vsDevice.h similarity index 95% rename from src/gui/vsDevice.h rename to src/vs/vsDevice.h index 99a2fe6..2ddb7f9 100644 --- a/src/gui/vsDevice.h +++ b/src/vs/vsDevice.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -73,7 +72,7 @@ class VsDevice : public QObject JackTrip* initJackTrip(bool useRtAudio, std::string input, std::string output, int baseInputChannel, int numChannelsIn, int baseOutputChannel, int numChannelsOut, int inputMixMode, int bufferSize, - int bufferStrategy, VsServerInfo* studioInfo); + int queueBuffer, VsServerInfo* studioInfo); void startJackTrip(const VsServerInfo& studioInfo); void stopJackTrip(bool isReconnecting = false); void reconcileAgentConfig(QJsonDocument newState); diff --git a/src/gui/vsDeviceCodeFlow.cpp b/src/vs/vsDeviceCodeFlow.cpp similarity index 96% rename from src/gui/vsDeviceCodeFlow.cpp rename to src/vs/vsDeviceCodeFlow.cpp index dbc17e5..4becfea 100644 --- a/src/gui/vsDeviceCodeFlow.cpp +++ b/src/vs/vsDeviceCodeFlow.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -65,6 +64,8 @@ void VsDeviceCodeFlow::grant() void VsDeviceCodeFlow::initDeviceAuthorizationCodeFlow() { + m_authenticationError = false; + // form initial request for device authorization code QNetworkRequest request = QNetworkRequest( QUrl(QString("https://%1/oauth/device/code").arg(m_authorizationServerHost))); @@ -87,7 +88,7 @@ void VsDeviceCodeFlow::initDeviceAuthorizationCodeFlow() emit deviceCodeFlowInitialized(m_userCode, m_verificationUriComplete); } else if (m_authenticationError) { // notify failure - emit deviceCodeFlowError(); + emit deviceCodeFlowError(reply->errorString()); } reply->deleteLater(); }); @@ -139,7 +140,7 @@ void VsDeviceCodeFlow::onPollingTimerTick() bool success = processPollingOAuthTokenNetworkReply(reply); if (m_authenticationError) { // shouldn't happen - emit deviceCodeFlowError(); + emit deviceCodeFlowError(reply->errorString()); } else if (success) { // flow successfully completed emit onCompletedCodeFlow(m_accessToken, m_refreshToken); @@ -172,7 +173,8 @@ bool VsDeviceCodeFlow::processDeviceCodeNetworkReply(QNetworkReply* reply) // Error: failed to get device code if (reply->error()) { - std::cout << "Failed to get device code: " << buffer.toStdString() << std::endl; + std::cout << "Failed to get device code: " << reply->errorString().toStdString() + << std::endl; m_authenticationError = true; return false; } diff --git a/src/gui/vsDeviceCodeFlow.h b/src/vs/vsDeviceCodeFlow.h similarity index 95% rename from src/gui/vsDeviceCodeFlow.h rename to src/vs/vsDeviceCodeFlow.h index eaadc7a..0bd91d0 100644 --- a/src/gui/vsDeviceCodeFlow.h +++ b/src/vs/vsDeviceCodeFlow.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation @@ -77,7 +76,7 @@ class VsDeviceCodeFlow : public QObject signals: void deviceCodeFlowInitialized(QString code, QString verificationUrl); - void deviceCodeFlowError(); + void deviceCodeFlowError(QString errorMessage); void deviceCodeFlowTimedOut(); void onCompletedCodeFlow(QString accessToken, QString refreshToken); diff --git a/src/gui/vsMacPermissions.h b/src/vs/vsMacPermissions.h similarity index 94% rename from src/gui/vsMacPermissions.h rename to src/vs/vsMacPermissions.h index 1360fa5..05fd873 100644 --- a/src/gui/vsMacPermissions.h +++ b/src/vs/vsMacPermissions.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsMacPermissions.mm b/src/vs/vsMacPermissions.mm similarity index 97% rename from src/gui/vsMacPermissions.mm rename to src/vs/vsMacPermissions.mm index a29c4ba..cca1300 100644 --- a/src/gui/vsMacPermissions.mm +++ b/src/vs/vsMacPermissions.mm @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsPermissions.cpp b/src/vs/vsPermissions.cpp similarity index 94% rename from src/gui/vsPermissions.cpp rename to src/vs/vsPermissions.cpp index a978a5a..b8c07db 100644 --- a/src/gui/vsPermissions.cpp +++ b/src/vs/vsPermissions.cpp @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsPermissions.h b/src/vs/vsPermissions.h similarity index 95% rename from src/gui/vsPermissions.h rename to src/vs/vsPermissions.h index cec2d97..f95c5b5 100644 --- a/src/gui/vsPermissions.h +++ b/src/vs/vsPermissions.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsPing.cpp b/src/vs/vsPing.cpp similarity index 95% rename from src/gui/vsPing.cpp rename to src/vs/vsPing.cpp index 2d1ec56..58b6b79 100644 --- a/src/gui/vsPing.cpp +++ b/src/vs/vsPing.cpp @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsPing.h b/src/vs/vsPing.h similarity index 94% rename from src/gui/vsPing.h rename to src/vs/vsPing.h index 228f814..fa4d3f8 100644 --- a/src/gui/vsPing.h +++ b/src/vs/vsPing.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsPinger.cpp b/src/vs/vsPinger.cpp similarity index 98% rename from src/gui/vsPinger.cpp rename to src/vs/vsPinger.cpp index 95207b6..1034af0 100644 --- a/src/gui/vsPinger.cpp +++ b/src/vs/vsPinger.cpp @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsPinger.h b/src/vs/vsPinger.h similarity index 96% rename from src/gui/vsPinger.h rename to src/vs/vsPinger.h index c171e7d..3319d14 100644 --- a/src/gui/vsPinger.h +++ b/src/vs/vsPinger.h @@ -3,8 +3,7 @@ JackTrip: A System for High-Quality Audio Network Performance over the Internet - Copyright (c) 2008-2021 Juan-Pablo Caceres, Chris Chafe. - SoundWIRE group at CCRMA, Stanford University. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/vs/vsQmlClipboard.h b/src/vs/vsQmlClipboard.h new file mode 100644 index 0000000..1bd544e --- /dev/null +++ b/src/vs/vsQmlClipboard.h @@ -0,0 +1,56 @@ +//***************************************************************** +/* + JackTrip: A System for High-Quality Audio Network Performance + over the Internet + + Copyright (c) 2022-2024 JackTrip Labs, Inc. + + 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. +*/ +//***************************************************************** + +#ifndef VSQMLCLIPBOARD_H +#define VSQMLCLIPBOARD_H + +#include +#include +#include + +class VsQmlClipboard : public QObject +{ + Q_OBJECT + public: + explicit VsQmlClipboard(QObject* parent = 0) : QObject(parent) + { + clipboard = QApplication::clipboard(); + } + + Q_INVOKABLE void setText(QString text) + { + clipboard->setText(text, QClipboard::Clipboard); + } + + private: + QClipboard* clipboard; +}; + +#endif // VSQMLCLIPBOARD_H \ No newline at end of file diff --git a/src/gui/vsQuickView.cpp b/src/vs/vsQuickView.cpp similarity index 95% rename from src/gui/vsQuickView.cpp rename to src/vs/vsQuickView.cpp index e0a9281..6143cbd 100644 --- a/src/gui/vsQuickView.cpp +++ b/src/vs/vsQuickView.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsQuickView.h b/src/vs/vsQuickView.h similarity index 94% rename from src/gui/vsQuickView.h rename to src/vs/vsQuickView.h index ab80a73..db7810d 100644 --- a/src/gui/vsQuickView.h +++ b/src/vs/vsQuickView.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsServerInfo.cpp b/src/vs/vsServerInfo.cpp similarity index 98% rename from src/gui/vsServerInfo.cpp rename to src/vs/vsServerInfo.cpp index 9ea5010..8d13a77 100644 --- a/src/gui/vsServerInfo.cpp +++ b/src/vs/vsServerInfo.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsServerInfo.h b/src/vs/vsServerInfo.h similarity index 98% rename from src/gui/vsServerInfo.h rename to src/vs/vsServerInfo.h index 5112c64..15f9d8f 100644 --- a/src/gui/vsServerInfo.h +++ b/src/vs/vsServerInfo.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsWebSocket.cpp b/src/vs/vsWebSocket.cpp similarity index 97% rename from src/gui/vsWebSocket.cpp rename to src/vs/vsWebSocket.cpp index d3ef34d..62946a0 100644 --- a/src/gui/vsWebSocket.cpp +++ b/src/vs/vsWebSocket.cpp @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/vsWebSocket.h b/src/vs/vsWebSocket.h similarity index 95% rename from src/gui/vsWebSocket.h rename to src/vs/vsWebSocket.h index 61b19c6..34da9b4 100644 --- a/src/gui/vsWebSocket.h +++ b/src/vs/vsWebSocket.h @@ -3,8 +3,7 @@ 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. + Copyright (c) 2022-2024 JackTrip Labs, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation diff --git a/src/gui/warning.svg b/src/vs/warning.svg similarity index 100% rename from src/gui/warning.svg rename to src/vs/warning.svg diff --git a/src/gui/wedge.svg b/src/vs/wedge.svg similarity index 100% rename from src/gui/wedge.svg rename to src/vs/wedge.svg diff --git a/src/gui/wedge_inactive.svg b/src/vs/wedge_inactive.svg similarity index 100% rename from src/gui/wedge_inactive.svg rename to src/vs/wedge_inactive.svg diff --git a/win/build_installer.bat b/win/build_installer.bat index 52c8229..a21fc61 100755 --- a/win/build_installer.bat +++ b/win/build_installer.bat @@ -55,9 +55,11 @@ if defined DYNAMIC_QT ( echo Including Qt%QTVERSION% Files for /f "tokens=*" %%a in ('objdump -p jacktrip.exe ^| findstr Qt%QTVERSION%Qml.dll') do set VS=%%a if defined VS ( - windeployqt -release --qmldir ..\..\src\gui jacktrip.exe + echo Including QML files + windeployqt -release --qmldir ..\..\src\vs jacktrip.exe set WIXDEFINES=%WIXDEFINES% -dvs ) else ( + echo Not including QML files windeployqt -release jacktrip.exe ) set WIXDEFINES=!WIXDEFINES! -ddynamic -dqt%QTVERSION% diff --git a/win/meson.build b/win/meson.build index 7362bba..b7ed4d4 100644 --- a/win/meson.build +++ b/win/meson.build @@ -31,6 +31,7 @@ if host_machine.system() == 'windows' link_args += 'Winhttp.lib' link_args += 'Dnsapi.lib' link_args += 'Iphlpapi.lib' + link_args += 'Qwave.lib' endif endif -- 2.30.2